Skip to content

fix compile button #6268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/remix-ide-e2e/src/tests/compile_run_widget.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ module.exports = {
return []
},

'Compile using the widget #group1': function (browser: NightwatchBrowser) {
'Compile using the widget #group1 #pr': function (browser: NightwatchBrowser) {
browser
.openFile('contracts/3_Ballot.sol')
.click('[data-id="compile-action"]')
.waitForElementVisible('[data-id="compile_group"] i.fa-check', 10000)
.verifyContracts(['Ballot'])
},

'Run script using the widget #group2': function (browser: NightwatchBrowser) {
'Run script using the widget #group2 #pr': function (browser: NightwatchBrowser) {
browser
.openFile('scripts/deploy_with_web3.ts')
.click('[data-id="compile-action"]')
.waitForElementVisible('[data-id="compile_group"] i.fa-check', 10000)
},

'Should activate Solidity Static Analysis and show "Solidity Analyzers" title from compile dropdown #group3': function (browser: NightwatchBrowser) {
'Should activate Solidity Static Analysis and show "Solidity Analyzers" title from compile dropdown #group3 #pr': function (browser: NightwatchBrowser) {
browser
.openFile('contracts/3_Ballot.sol')
.click('[data-id="compile-dropdown-trigger"]')
Expand All @@ -42,7 +42,7 @@ module.exports = {
.verifyContracts(['Ballot'])
},

'Should run Solidity Scan and display results in terminal #group4': function (browser: NightwatchBrowser) {
'Should run Solidity Scan and display results in terminal #group4 #pr': function (browser: NightwatchBrowser) {
browser
.openFile('contracts/3_Ballot.sol')
.click('[data-id="compile-dropdown-trigger"]')
Expand Down
118 changes: 91 additions & 27 deletions libs/remix-ui/tabs/src/lib/components/CompileDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,106 @@ interface CompileDropdownProps {
plugin?: any
disabled?: boolean
compiledFileName?: string
onNotify?: (msg: string) => void
onOpen?: () => void
onRequestCompileAndPublish?: (type: string) => void
setCompileState: (state: 'idle' | 'compiling' | 'compiled') => void
}

export const CompileDropdown: React.FC<CompileDropdownProps> = ({ tabPath, plugin, disabled, onNotify, onOpen, onRequestCompileAndPublish, compiledFileName, setCompileState }) => {
export const CompileDropdown: React.FC<CompileDropdownProps> = ({ tabPath, plugin, disabled, onOpen, onRequestCompileAndPublish, compiledFileName, setCompileState }) => {
const [scriptFiles, setScriptFiles] = useState<string[]>([])

const compileThen = async (nextAction: () => void) => {
const compileThen = async (nextAction: () => void, actionName: string) => {
setCompileState('compiling')

setTimeout(async () => {
plugin.once('solidity', 'compilationFinished', (data) => {
const hasErrors = data.errors && data.errors.filter(e => e.severity === 'error').length > 0
if (hasErrors) {
setCompileState('idle')
plugin.call('notification', 'toast', 'Compilation failed')
} else {
setCompileState('compiled')
nextAction()
try {
await plugin.call('fileManager', 'saveCurrentFile')
await plugin.call('manager', 'activatePlugin', 'solidity')

const startedAt = Date.now()
const targetPath = tabPath || ''

const waitForFreshCompilationResult = async (
path: string,
startMs: number,
maxWaitMs = 1500,
intervalMs = 120
) => {
const norm = (p: string) => p.replace(/^\/+/, '')
const fileName = (norm(path).split('/').pop() || norm(path)).toLowerCase()
const hasFile = (res: any) => {
if (!res) return false
const inContracts =
res.contracts && typeof res.contracts === 'object' &&
Object.keys(res.contracts).some(k => k.toLowerCase().endsWith(fileName) || norm(k).toLowerCase() === norm(path).toLowerCase())
const inSources =
res.sources && typeof res.sources === 'object' &&
Object.keys(res.sources).some(k => k.toLowerCase().endsWith(fileName) || norm(k).toLowerCase() === norm(path).toLowerCase())
return inContracts || inSources
}
})

try {
await plugin.call('solidity', 'compile', tabPath)
} catch (e) {
console.error(e)
let last: any = null
const until = startMs + maxWaitMs
while (Date.now() < until) {
try {
const res = await plugin.call('solidity', 'getCompilationResult')
last = res
const ts = (res && (res.timestamp || res.timeStamp || res.time || res.generatedAt)) || null
const isFreshTime = typeof ts === 'number' ? ts >= startMs : true
if (res && hasFile(res) && isFreshTime) return res
} catch {}
await new Promise(r => setTimeout(r, intervalMs))
}
return last
}

let settled = false
let watchdog: NodeJS.Timeout | null = null
const cleanup = () => {
try { plugin.off('solidity', 'compilationFinished', onFinished) } catch {}
if (watchdog) { clearTimeout(watchdog); watchdog = null }
}

const finishWithErrorUI = async () => {
setCompileState('idle')
await plugin.call('manager', 'activatePlugin', 'solidity')
await plugin.call('menuicons', 'select', 'solidity')
plugin.call('notification', 'toast', `Compilation failed, skipping '${actionName}'.`)
}
}, 0)

const onFinished = async () => {
if (settled) return
settled = true
cleanup()

const fresh = await waitForFreshCompilationResult(targetPath, startedAt).catch(() => null)
if (!fresh) {
await finishWithErrorUI()
return
}

const errs = Array.isArray(fresh.errors)
? fresh.errors.filter((e: any) => (e.severity || e.type) === 'error')
: []

if (errs.length > 0) {
await finishWithErrorUI()
return
}

setCompileState('compiled')
nextAction()
}

plugin.on('solidity', 'compilationFinished', onFinished)

watchdog = setTimeout(() => { onFinished() }, 10000)

await plugin.call('solidity', 'compile', targetPath)

} catch (e) {
console.error(e)
setCompileState('idle')
}
}

const fetchScripts = async () => {
Expand All @@ -55,19 +124,16 @@ export const CompileDropdown: React.FC<CompileDropdownProps> = ({ tabPath, plugi
}
const tsFiles = Object.keys(files).filter(f => f.endsWith('.ts'))
setScriptFiles(tsFiles)
onNotify?.(`Loaded ${tsFiles.length} script files`)
} catch (err) {
console.error("Failed to read scripts directory:", err)
onNotify?.("Failed to read scripts directory")
}
}

const runScript = async (path: string) => {
await compileThen(async () => {
const content = await plugin.call('fileManager', 'readFile', path)
await plugin.call('scriptRunnerBridge', 'execute', content, path)
onNotify?.(`Executed script: ${path}`)
})
}, 'Run Script')
}

const runRemixAnalysis = async () => {
Expand All @@ -78,16 +144,15 @@ export const CompileDropdown: React.FC<CompileDropdownProps> = ({ tabPath, plugi
await plugin.call('manager', 'activatePlugin', 'solidityStaticAnalysis')
}
plugin.call('menuicons', 'select', 'solidityStaticAnalysis')
onNotify?.("Ran Remix static analysis")
})
}, 'Run Remix Analysis')
}

const handleScanContinue = async () => {
await compileThen(async () => {
const firstSlashIndex = compiledFileName.indexOf('/')
const finalPath = firstSlashIndex > 0 ? compiledFileName.substring(firstSlashIndex + 1) : compiledFileName
await handleSolidityScan(plugin, finalPath)
})
}, 'Run Solidity Scan')
}

const runSolidityScan = async () => {
Expand Down Expand Up @@ -119,7 +184,6 @@ export const CompileDropdown: React.FC<CompileDropdownProps> = ({ tabPath, plugi
await plugin.call('manager', 'activatePlugin', 'solidity')
}
plugin.call('menuicons', 'select', 'solidity')
onNotify?.("Ran Remix Solidity Compiler")
}

const items: MenuItem[] = [
Expand All @@ -139,7 +203,7 @@ export const CompileDropdown: React.FC<CompileDropdownProps> = ({ tabPath, plugi
icon: <ArrowRightBig />,
dataId: 'compile-run-analysis-menu-item',
submenu: [
{ label: 'Run Remix Analysis', icon: <AnalysisLogo />, onClick: runRemixAnalysis, dataId: 'run-remix-analysis-submenu-item' },
{ label: 'Run Remix Analysis', icon: <SettingsLogo />, onClick: runRemixAnalysis, dataId: 'run-remix-analysis-submenu-item' },
{ label: 'Run Solidity Scan', icon: <SolidityScanLogo />, onClick: runSolidityScan, dataId: 'run-solidity-scan-submenu-item' }
]
},
Expand Down
130 changes: 66 additions & 64 deletions libs/remix-ui/tabs/src/lib/components/DropdownMenu.css
Original file line number Diff line number Diff line change
@@ -1,87 +1,89 @@
.custom-dropdown-wrapper {
position: relative;
display: inline-block;
z-index: 1000;
position: relative;
display: inline-block;
z-index: 1000;
}

.custom-dropdown-trigger {
/* color: var(--text); */
/* background: var(--custom-select, #2d2f3b); */
padding: 4px 8px;
border-left: 1px solid var(--bs-secondary);
border-radius: 0 4px 4px 0;
cursor: pointer;
height: 28px;
font-size: 11px;
font-weight: 700;
padding: 4px 8px;
border-left: 1px solid var(--bs-border-color, var(--bs-secondary));
border-radius: 0 4px 4px 0;
cursor: pointer;
height: 28px;
font-size: 11px;
font-weight: 700;
}

.custom-dropdown-panel {
background: var(--custom-select);
border: 1px solid var(--bs-secondary);
border-radius: 4px;
position: absolute;
top: calc(100% + 4px);
left: 0;
color: var(--text);
padding: 0;
.custom-dropdown-panel.dropdown-menu {
padding: 0;
min-width: auto;
z-index: 1310;
isolation: isolate;
background-color: var(--bs-dropdown-bg, var(--bs-body-bg)) !important;
color: var(--bs-dropdown-color, var(--bs-body-color));
border: 1px solid var(--bs-border-color);
border-radius: var(--bs-border-radius, .375rem);
box-shadow: var(--bs-box-shadow, 0 .5rem 1rem rgba(0,0,0,.15));
}

.custom-dropdown-item {
display: flex;
align-items: center;
gap: 5px;
padding: 8px 16px;
color: var(--text);
cursor: pointer;
white-space: nowrap;
position: relative
.custom-dropdown-item.dropdown-item {
display: flex;
align-items: center;
gap: 5px;
color: var(--bs-dropdown-link-color, var(--bs-body-color));
white-space: nowrap;
position: relative;
padding: 8px 16px;
background-color: transparent;
}

.custom-dropdown-item-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
display: block;
align-self: center;
line-height: 1;
width: 16px;
height: 16px;
flex-shrink: 0;
display: block;
line-height: 1;
}

.custom-dropdown-item > span:not(.custom-dropdown-item-icon) {
flex-grow: 1;
display: flex;
align-items: center;
margin-right: 12px;
flex-grow: 1;
display: flex;
align-items: center;
margin-right: 12px;
}

.custom-dropdown-item .custom-dropdown-item-icon:last-child {
margin-left: auto;
margin-left: auto;
}

.custom-dropdown-item:hover {
background: var(--bs-secondary);
.custom-dropdown-item.dropdown-item:hover,
.custom-dropdown-submenu .custom-dropdown-item.dropdown-item:hover,
.custom-dropdown-item.dropdown-item:focus,
.custom-dropdown-submenu .custom-dropdown-item.dropdown-item:focus {
background-color: var(--bs-dropdown-link-hover-bg) !important;
color: var(--bs-dropdown-link-hover-color, var(--bs-body-color)) !important;
outline: none;
z-index: 1330;
}

.custom-dropdown-submenu {
position: absolute;
top: 0;
left: 100%;
background: var(--custom-select);
border: 1px solid var(--bs-secondary);
border-radius: 4px;
min-width: 200px;
color: var(--text);
}
.custom-dropdown-item.disabled { opacity: 0.4; pointer-events: none; }
.custom-dropdown-item.border-top { border-top: 1px solid var(--bs-border-color); }
.custom-dropdown-item.border-bottom { border-bottom: 1px solid var(--bs-border-color); }

.custom-dropdown-item.disabled {
opacity: 0.4;
pointer-events: none;
}
.custom-dropdown-item.has-submenu { position: relative; }
.custom-dropdown-submenu.dropdown-menu {
position: absolute;
top: 0;
left: calc(100% - var(--bs-border-width, 1px));
min-width: 200px;
padding: 0;
z-index: 1320;
transform: translateZ(0);
box-shadow: var(--bs-box-shadow);

.custom-dropdown-item.border-top {
border-top: 1px solid var(--bs-secondary);
background-color: var(--bs-dropdown-bg, var(--bs-body-bg)) !important;
color: var(--bs-dropdown-color, var(--bs-body-color));
border: 1px solid var(--bs-border-color);
border-radius: var(--bs-border-radius, .375rem);
}

.custom-dropdown-item.border-bottom {
border-bottom: 1px solid var(--bs-secondary);
.custom-dropdown-submenu .custom-dropdown-item.bg-light {
background-color: transparent !important;
}
Loading