Commit 38b620c6 authored by tom's avatar tom

basic implementation

parent b0368b9a
...@@ -8,6 +8,7 @@ function generateCspPolicy() { ...@@ -8,6 +8,7 @@ function generateCspPolicy() {
descriptors.googleAnalytics(), descriptors.googleAnalytics(),
descriptors.googleFonts(), descriptors.googleFonts(),
descriptors.googleReCaptcha(), descriptors.googleReCaptcha(),
descriptors.monaco(),
descriptors.sentry(), descriptors.sentry(),
descriptors.walletConnect(), descriptors.walletConnect(),
); );
......
...@@ -3,5 +3,6 @@ export { app } from './app'; ...@@ -3,5 +3,6 @@ export { app } from './app';
export { googleAnalytics } from './googleAnalytics'; export { googleAnalytics } from './googleAnalytics';
export { googleFonts } from './googleFonts'; export { googleFonts } from './googleFonts';
export { googleReCaptcha } from './googleReCaptcha'; export { googleReCaptcha } from './googleReCaptcha';
export { monaco } from './monaco';
export { sentry } from './sentry'; export { sentry } from './sentry';
export { walletConnect } from './walletConnect'; export { walletConnect } from './walletConnect';
import type CspDev from 'csp-dev';
import { KEY_WORDS } from '../utils';
export function monaco(): CspDev.DirectiveDescriptor {
return {
'script-src': [
KEY_WORDS.BLOB,
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/loader.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.nls.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/solidity/solidity.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/base/worker/workerMain.js',
],
'style-src': [
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.css',
],
'font-src': [
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/base/browser/ui/codicons/codicon/codicon.ttf',
],
};
}
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
"@emotion/react": "^11.10.4", "@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4", "@emotion/styled": "^11.10.4",
"@metamask/providers": "^10.2.1", "@metamask/providers": "^10.2.1",
"@monaco-editor/react": "^4.4.6",
"@sentry/nextjs": "^7.12.1", "@sentry/nextjs": "^7.12.1",
"@sentry/react": "^7.24.0", "@sentry/react": "^7.24.0",
"@sentry/tracing": "^7.24.0", "@sentry/tracing": "^7.24.0",
...@@ -43,7 +44,6 @@ ...@@ -43,7 +44,6 @@
"@types/react-scroll": "^1.8.4", "@types/react-scroll": "^1.8.4",
"@web3modal/ethereum": "^2.0.0-rc.2", "@web3modal/ethereum": "^2.0.0-rc.2",
"@web3modal/react": "^2.0.0-rc.2", "@web3modal/react": "^2.0.0-rc.2",
"ace-builds": "^1.14.0",
"bignumber.js": "^9.1.0", "bignumber.js": "^9.1.0",
"chakra-react-select": "^4.4.3", "chakra-react-select": "^4.4.3",
"d3": "^7.6.1", "d3": "^7.6.1",
...@@ -56,6 +56,8 @@ ...@@ -56,6 +56,8 @@
"graphql-ws": "^5.11.3", "graphql-ws": "^5.11.3",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.0.0", "lodash": "^4.0.0",
"monaco-editor": "^0.34.1",
"monaco-editor-webpack-plugin": "^7.0.1",
"next": "12.2.5", "next": "12.2.5",
"nextjs-routes": "^1.0.8", "nextjs-routes": "^1.0.8",
"node-fetch": "^3.2.9", "node-fetch": "^3.2.9",
...@@ -66,7 +68,6 @@ ...@@ -66,7 +68,6 @@
"pino-pretty": "^9.1.1", "pino-pretty": "^9.1.1",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"react": "18.2.0", "react": "18.2.0",
"react-ace": "^10.1.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-google-recaptcha": "^2.1.0", "react-google-recaptcha": "^2.1.0",
"react-hook-form": "^7.33.1", "react-hook-form": "^7.33.1",
...@@ -96,6 +97,7 @@ ...@@ -96,6 +97,7 @@
"@types/swagger-ui-react": "^4.11.0", "@types/swagger-ui-react": "^4.11.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.53.0", "@typescript-eslint/eslint-plugin": "^5.53.0",
"css-loader": "^6.7.3",
"dotenv-cli": "^6.0.0", "dotenv-cli": "^6.0.0",
"eslint": "^8.32.0", "eslint": "^8.32.0",
"eslint-config-next": "^12.3.0", "eslint-config-next": "^12.3.0",
...@@ -112,6 +114,7 @@ ...@@ -112,6 +114,7 @@
"mockdate": "^3.0.5", "mockdate": "^3.0.5",
"next-transpile-modules": "^10.0.0", "next-transpile-modules": "^10.0.0",
"playwright": "1.31.0", "playwright": "1.31.0",
"style-loader": "^3.3.1",
"svgo": "^2.8.0", "svgo": "^2.8.0",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
......
import { Box, chakra, Flex, Text, Tooltip } from '@chakra-ui/react'; import { Flex, Text, Tooltip } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import type { SmartContract } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
import CodeEditor from 'ui/shared/CodeEditor'; import CodeEditorMonaco from 'ui/shared/CodeEditorMonaco';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
...@@ -37,18 +36,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -37,18 +36,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
</Tooltip> </Tooltip>
) : null; ) : null;
if (!additionalSource?.length) { const code = [ { file_path: filePath || 'index.sol', source_code: data }, ...(additionalSource || []) ];
return (
<section>
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
{ heading }
{ diagramLink }
<CopyToClipboard text={ data }/>
</Flex>
<CodeEditor value={ data } id="source_code"/>
</section>
);
}
return ( return (
<section> <section>
...@@ -56,19 +44,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -56,19 +44,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
{ heading } { heading }
{ diagramLink } { diagramLink }
</Flex> </Flex>
<Flex flexDir="column" rowGap={ 3 }> <CodeEditorMonaco data={ code }/>
{ [ { file_path: filePath, source_code: data }, ...additionalSource ].map((item, index, array) => (
<Box key={ index }>
<Flex justifyContent="space-between" alignItems="flex-end" mb={ 3 }>
<chakra.span fontSize="sm" wordBreak="break-all" lineHeight="20px">
File { index + 1 } of { array.length }: { item.file_path }
</chakra.span>
<CopyToClipboard text={ item.source_code } ml={ 4 }/>
</Flex>
<CodeEditor value={ item.source_code } id={ `source_code_${ index }` }/>
</Box>
)) }
</Flex>
</section> </section>
); );
}; };
......
import { chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type ReactAce from 'react-ace/lib/ace';
interface Props {
id: string;
value: string;
className?: string;
}
const CodeEditorBase = chakra(({ id, value, className }: Props) => {
const [ AceEditor, setAceEditor ] = React.useState<{default: typeof ReactAce} | null>(null);
React.useEffect(() => {
const load = async() => {
const component = await import('react-ace');
await import('ace-builds/src-noconflict/mode-csharp');
await import('ace-builds/src-noconflict/theme-tomorrow');
await import('ace-builds/src-noconflict/theme-tomorrow_night');
await import('ace-builds/src-noconflict/ext-language_tools');
setAceEditor(component);
};
load();
return () => {
setAceEditor(null);
};
}, []);
const theme = useColorModeValue('tomorrow', 'tomorrow_night');
if (!AceEditor) {
return null;
}
return (
<AceEditor.default
className={ className }
mode="csharp" // TODO need to find mode for solidity
theme={ theme }
value={ value }
name={ id }
editorProps={{ $blockScrolling: true }}
readOnly
width="100%"
showPrintMargin={ false }
maxLines={ 25 }
/>
);
});
const CodeEditor = ({ id, value }: Props) => {
// see theme/components/Textarea.ts variantFilledInactive
const bgColor = useColorModeValue('#f5f5f6', '#1a1b1b');
const gutterBgColor = useColorModeValue('gray.100', '#25282c');
return (
<CodeEditorBase
id={ id }
value={ value }
bgColor={ bgColor }
borderRadius="md"
overflow="hidden"
sx={{
'.ace_gutter': {
backgroundColor: gutterBgColor,
},
}}
/>
);
};
export default React.memo(CodeEditor);
import { Box, useColorMode } from '@chakra-ui/react';
import Editor from '@monaco-editor/react';
import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import React from 'react';
export type Monaco = typeof monaco;
interface Props {
data: Array<{ file_path: string; source_code: string }>;
}
const CodeEditorMonaco = ({ data }: Props) => {
const instance = React.useRef<Monaco>();
const { colorMode } = useColorMode();
React.useEffect(() => {
instance.current?.editor.setTheme(colorMode === 'light' ? 'blockscout-light' : 'blockscout-dark');
}, [ colorMode ]);
const handleEditorDidMount = React.useCallback((editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) => {
instance.current = monaco;
monaco.editor.defineTheme('blockscout-light', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editor.background': '#f5f5f6',
},
});
monaco.editor.defineTheme('blockscout-dark', {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#1a1b1b',
},
});
monaco.editor.setTheme(colorMode === 'light' ? 'blockscout-light' : 'blockscout-dark');
// componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ ]);
return (
<Box overflow="hidden" borderRadius="md">
<Editor
height="500px"
language="sol"
defaultValue={ data[0].source_code }
options={{ readOnly: true, inlayHints: { enabled: 'off' }, minimap: { enabled: false } }}
onMount={ handleEditorDidMount }
/>
</Box>
);
};
export default CodeEditorMonaco;
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment