diff --git a/public/static/contract_star.png b/public/static/contract_star.png new file mode 100644 index 0000000000..32d635ef60 Binary files /dev/null and b/public/static/contract_star.png differ diff --git a/ui/address/contract/ContractSourceCode.tsx b/ui/address/contract/ContractSourceCode.tsx index 4d5356eb65..1766479979 100644 --- a/ui/address/contract/ContractSourceCode.tsx +++ b/ui/address/contract/ContractSourceCode.tsx @@ -163,6 +163,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { libraries={ primaryContractQuery.data?.external_libraries ?? undefined } language={ primaryContractQuery.data?.language ?? undefined } mainFile={ primaryEditorData[0]?.file_path } + contractName={ primaryContractQuery.data?.name || undefined } /> { secondaryEditorData && ( @@ -173,6 +174,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { libraries={ secondaryContractQuery.data?.external_libraries ?? undefined } language={ secondaryContractQuery.data?.language ?? undefined } mainFile={ secondaryEditorData?.[0]?.file_path } + contractName={ secondaryContractQuery.data?.name || undefined } /> ) } diff --git a/ui/shared/monaco/CodeEditor.tsx b/ui/shared/monaco/CodeEditor.tsx index 31182c8a9a..7a39734c95 100644 --- a/ui/shared/monaco/CodeEditor.tsx +++ b/ui/shared/monaco/CodeEditor.tsx @@ -19,6 +19,7 @@ import CodeEditorSideBar, { CONTAINER_WIDTH as SIDE_BAR_WIDTH } from './CodeEdit import CodeEditorTabs from './CodeEditorTabs'; import addExternalLibraryWarningDecoration from './utils/addExternalLibraryWarningDecoration'; import addFileImportDecorations from './utils/addFileImportDecorations'; +import addMainContractCodeDecoration from './utils/addMainContractCodeDecoration'; import getFullPathOfImportedFile from './utils/getFullPathOfImportedFile'; import * as themes from './utils/themes'; import useThemeColors from './utils/useThemeColors'; @@ -42,9 +43,10 @@ interface Props { libraries?: Array; language?: string; mainFile?: string; + contractName?: string; } -const CodeEditor = ({ data, remappings, libraries, language, mainFile }: Props) => { +const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractName }: Props) => { const [ instance, setInstance ] = React.useState(); const [ editor, setEditor ] = React.useState(); const [ index, setIndex ] = React.useState(0); @@ -82,9 +84,10 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile }: Props) if (language === 'solidity') { loadedModels.concat(newModels) - .forEach((models) => { - addFileImportDecorations(models); - libraries?.length && addExternalLibraryWarningDecoration(models, libraries); + .forEach((model) => { + contractName && mainFile === model.uri.path && addMainContractCodeDecoration(model, contractName, editor); + addFileImportDecorations(model); + libraries?.length && addExternalLibraryWarningDecoration(model, libraries); }); } @@ -192,6 +195,13 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile }: Props) '.monaco-editor .overflow-guard': { 'border-bottom-left-radius': borderRadius, }, + '.monaco-editor .core-guide': { + zIndex: 1, + }, + // '.monaco-editor .currentFindMatch': // TODO: find a better way to style this + '.monaco-editor .findMatch': { + backgroundColor: themeColors['custom.findMatchHighlightBackground'], + }, '.highlight': { backgroundColor: themeColors['custom.findMatchHighlightBackground'], }, @@ -206,6 +216,18 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile }: Props) '.risk-warning': { backgroundColor: themeColors['custom.riskWarning.background'], }, + '.main-contract-header': { + backgroundColor: themeColors['custom.mainContract.header'], + }, + '.main-contract-body': { + backgroundColor: themeColors['custom.mainContract.body'], + }, + '.main-contract-glyph': { + zIndex: 1, + background: 'url(/static/contract_star.png) no-repeat center center', + backgroundSize: '12px', + cursor: 'pointer', + }, }), [ editorWidth, themeColors, borderRadius ]); const renderErrorScreen = React.useCallback(() => { diff --git a/ui/shared/monaco/utils/addExternalLibraryWarningDecoration.ts b/ui/shared/monaco/utils/addExternalLibraryWarningDecoration.ts index 81a490fa5f..9d77966303 100644 --- a/ui/shared/monaco/utils/addExternalLibraryWarningDecoration.ts +++ b/ui/shared/monaco/utils/addExternalLibraryWarningDecoration.ts @@ -2,6 +2,8 @@ import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import type { SmartContractExternalLibrary } from 'types/api/contract'; +import sortByEndLineNumberAsc from './sortByEndLineNumberAsc'; + export default function addExternalLibraryWarningDecoration(model: monaco.editor.ITextModel, libraries: Array) { const options: monaco.editor.IModelDecorationOptions = { isWholeLine: true, @@ -81,15 +83,3 @@ const getLibraryName = (model: monaco.editor.ITextModel) => (library: SmartContr return libraryName; }; - -const sortByEndLineNumberAsc = (a: monaco.editor.FindMatch, b: monaco.editor.FindMatch) => { - if (a.range.endLineNumber < b.range.endLineNumber) { - return -1; - } - - if (a.range.endLineNumber > b.range.endLineNumber) { - return 1; - } - - return 0; -}; diff --git a/ui/shared/monaco/utils/addMainContractCodeDecoration.ts b/ui/shared/monaco/utils/addMainContractCodeDecoration.ts new file mode 100644 index 0000000000..1c2e3c5e0c --- /dev/null +++ b/ui/shared/monaco/utils/addMainContractCodeDecoration.ts @@ -0,0 +1,66 @@ +import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +import sortByEndLineNumberAsc from './sortByEndLineNumberAsc'; + +export default function addMainContractCodeDecoration(model: monaco.editor.ITextModel, contractName: string, editor: monaco.editor.IStandaloneCodeEditor) { + const options: monaco.editor.IModelDecorationOptions = { + isWholeLine: true, + }; + + const contractBlockMatches = model.findMatches(`^contract\\s`, false, true, false, null, true); + + if (contractBlockMatches.length < 2) { + return; + } + + const [ firstLineMatch ] = model.findMatches(`(^contract ${ contractName })( is .+)?\\s?\\{`, false, true, false, null, true); + + if (!firstLineMatch) { + return; + } + + const firstLineDecoration: monaco.editor.IModelDeltaDecoration = { + range: { + startColumn: 1, + endColumn: 10, // doesn't really matter since isWholeLine is true + startLineNumber: firstLineMatch.range.startLineNumber, + endLineNumber: firstLineMatch.range.startLineNumber, + }, + options: { + ...options, + className: '.main-contract-header', + marginClassName: '.main-contract-header', + glyphMarginClassName: '.main-contract-glyph', + glyphMarginHoverMessage: [ + { value: 'Main contract' }, + ], + }, + }; + + const lastLineRange: monaco.IRange = { + startLineNumber: firstLineMatch.range.startLineNumber, + startColumn: 1, + endColumn: 10, + endLineNumber: model.getLineCount(), + }; + const [ lastLineMatch ] = model + .findMatches(`^\\}`, lastLineRange, true, false, null, true) + .sort(sortByEndLineNumberAsc); + + const restDecoration: monaco.editor.IModelDeltaDecoration = { + range: { + startLineNumber: firstLineMatch.range.startLineNumber + 1, + endLineNumber: lastLineMatch.range.startLineNumber, + startColumn: 1, + endColumn: 10, // doesn't really matter since isWholeLine is true + }, + options: { + ...options, + className: '.main-contract-body', + marginClassName: '.main-contract-body', + }, + }; + + editor.updateOptions({ glyphMargin: true }); + model.deltaDecorations([], [ firstLineDecoration, restDecoration ]); +} diff --git a/ui/shared/monaco/utils/sortByEndLineNumberAsc.ts b/ui/shared/monaco/utils/sortByEndLineNumberAsc.ts new file mode 100644 index 0000000000..08fc4536e6 --- /dev/null +++ b/ui/shared/monaco/utils/sortByEndLineNumberAsc.ts @@ -0,0 +1,13 @@ +import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +export default function sortByEndLineNumberAsc(a: monaco.editor.FindMatch, b: monaco.editor.FindMatch) { + if (a.range.endLineNumber < b.range.endLineNumber) { + return -1; + } + + if (a.range.endLineNumber > b.range.endLineNumber) { + return 1; + } + + return 0; +} diff --git a/ui/shared/monaco/utils/themes.ts b/ui/shared/monaco/utils/themes.ts index 238504c4eb..14a861a3df 100644 --- a/ui/shared/monaco/utils/themes.ts +++ b/ui/shared/monaco/utils/themes.ts @@ -37,6 +37,8 @@ export const light = { 'custom.fileLink.hoverForeground': '#4299E1', // blue.400 'custom.riskWarning.primaryBackground': '#FEEBCB', // orange.100 'custom.riskWarning.background': '#FFFAF0', // orange.50 + 'custom.mainContract.header': 'rgba(233, 216, 253, 1)', // purple.100 + 'custom.mainContract.body': 'rgba(250, 245, 255, 1)', // purple.50 } as const, }; @@ -79,5 +81,7 @@ export const dark = { 'custom.fileLink.hoverForeground': '#4299E1', // blue.400 'custom.riskWarning.primaryBackground': 'rgba(246, 173, 85, 0.3)', // orange.300 'custom.riskWarning.background': 'rgba(246, 173, 85, 0.1)', // orange.300 + 'custom.mainContract.header': 'rgba(183, 148, 244, 0.3)', // purple.300 + 'custom.mainContract.body': 'rgba(214, 188, 250, 0.1)', // purple.200 } as const, };