Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a85c16d
Add plumbing for template explorer to modal plugin
Sep 8, 2025
cba3241
setup and state changes for template explorer modal
Sep 8, 2025
ddeb1ac
create template explorer modal. modify app context.
Sep 15, 2025
b978a39
rename plugin files appropriately.
Sep 16, 2025
07e8dbe
rename plugin file. Add ts compiler option
Sep 17, 2025
8f79174
fix styles. add logo update helpers.tsx
Sep 22, 2025
70855e8
fix search to filter and filter by tag. fix order of components
Sep 23, 2025
6b1803a
finished contract wizard screen
Sep 26, 2025
e3dde7a
update types. Working on miniFile Explorer and workspace details
Sep 29, 2025
52509fc
fix dom element nesting error
Sep 29, 2025
791aa0b
styling icons
Sep 29, 2025
52db1e7
fix styling of editor. fix styling of miniexplorer
Sep 30, 2025
575e2dd
connect screens statefully. update zeppelin wizard code
Oct 7, 2025
b625fcc
update types and the reducer
Oct 7, 2025
ef98b46
consolidate reducer and state. Add logic for workspace creation.
Oct 8, 2025
b976769
contract file changes
Oct 13, 2025
e475c0b
light up topcards. add behaviour to facade
Oct 16, 2025
9102942
load readme for template
Oct 17, 2025
d4386ee
reusable markdown component
Oct 17, 2025
3d554e0
final screen in wizard.
Oct 20, 2025
1b32f29
finish fixing workspace generation bug. Looking at tests
Oct 23, 2025
47041e1
Merge branch 'master' into extend-modal
joeizang Oct 24, 2025
d29fb1c
cleanup and fix imports
Oct 24, 2025
861149f
clean up and fix reading readme for blank template
Oct 24, 2025
aa41c0c
add e2e for template exp modal
Oct 24, 2025
24857cf
remove test button
Oct 24, 2025
cc1f43e
cleanup and fixes. Notification after AI workspace generation
Oct 24, 2025
b27f753
add more e2es. cleaning up contract wizard
Oct 27, 2025
f59c71f
add more tests
Oct 28, 2025
41598eb
Merge branch 'master' into extend-modal
joeizang Oct 28, 2025
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
186 changes: 186 additions & 0 deletions apps/remix-ide-e2e/src/tests/template_exp_modal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'

module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Create blank workspace': function (browser: NightwatchBrowser) {
browser
.refreshPage()
.waitForElementVisible('*[data-id="workspacesSelect"]')
.click('*[data-id="workspacesSelect')
.pause(3000)
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="template-explorer-modal-react"]')
.waitForElementVisible('*[data-id="template-card-blank-1"]')
.click('*[data-id="template-card-blank-1"]')
.waitForElementVisible('*[data-id="workspace-name-blank-input"]')
.click('*[data-id="workspace-name-blank-input"]')
.pause(1000)
.setValue('*[data-id="workspace-name-blank-input"]', 'Test Blank Workspace')
.click('*[data-id="validate-blankworkspace-button"]')
.pause(1000)
.assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test Blank Workspace', 'Workspace name is correct')
.isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]')
.isVisible('*[data-id="treeViewDivDraggableItem.prettierrc.json"]')
.waitForElementNotPresent('*[data-id="treeViewDivDraggableItemcontracts"]')
},
'Create Pectra 7702 based workspace': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesSelect"]')
.pause(3000)
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="template-card-simpleEip7702-2"]')
.click('*[data-id="template-card-simpleEip7702-2"]')
.waitForElementVisible('*[data-id="workspace-name-simpleEip7702-input"]')
.click('*[data-id="workspace-name-simpleEip7702-input"]')
.setValue('*[data-id="workspace-name-simpleEip7702-input"]', 'Test Pectra 7702 Workspace')
.click('*[data-id="validate-simpleEip7702workspace-button"]')
.pause(1000)
.assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test Pectra 7702 Workspace', 'Workspace name is correct')
.isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]')
.waitForElementVisible('*[data-id="treeViewDivDraggableItemcontracts"]')
.isVisible('*[data-id="treeViewDivDraggableItemcontracts/Example7702.sol"]')
.waitForElementNotPresent('*[data-id="treeViewDivDraggableItemtests"]')
},
'Create Semaphore based workspace': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesSelect"]')
.pause(3000)
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="template-explorer-template-container"]')
.click('*[data-id="template-explorer-template-container"]')
.scrollInto('*[data-id="template-category-Circom ZKP"]')
.waitForElementVisible('*[data-id="template-card-semaphore-0"]')
.click('*[data-id="template-card-semaphore-0"]')
.waitForElementVisible('*[data-id="workspace-name-semaphore-input"]')
.click('*[data-id="workspace-name-semaphore-input"]')
.setValue('*[data-id="workspace-name-semaphore-input"]', 'Test Semaphore Workspace')
.click('*[data-id="validate-semaphoreworkspace-button"]')
.pause(1000)
.assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test Semaphore Workspace', 'Workspace name is correct')
.isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]')
.waitForElementVisible('*[data-id="treeViewDivDraggableItemcircuits"]')
.isVisible('*[data-id="treeViewDivDraggableItemcircuits/semaphore.circom"]')
.waitForElementNotPresent('*[data-id="treeViewDivDraggableItemtests"]')
.click('*[data-id="treeViewDivDraggableItemcircuits/semaphore.circom"]')
.waitForElementVisible('*[data-id="compile-action"]')
.click('*[data-id="compile-action"]')
.pause(3000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Everything went okay', 60000)
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="treeViewDivDraggableItemcircuits/.bin/semaphore_js"]')
},
'Search for Noir Simple Multiplier template': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesSelect"]')
.pause(3000)
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="template-explorer-search-input"]')
.click('*[data-id="template-explorer-search-input"]')
.setValue('*[data-id="template-explorer-search-input"]', 'Simple Multiplier')
.pause(1000)
.waitForElementVisible('*[data-id="template-card-multNr-0"]')
.click('*[data-id="template-card-multNr-0"]')
.waitForElementVisible('*[data-id="workspace-name-multNr-input"]')
.click('*[data-id="workspace-name-multNr-input"]')
.setValue('*[data-id="workspace-name-multNr-input"]', 'Test Simple Multiplier Workspace')
.click('*[data-id="validate-multNrworkspace-button"]')
.waitForElementVisible('*[data-id="treeViewDivDraggableItemNargo.toml"]')
.isVisible('*[data-id="treeViewDivDraggableItemsrc"]')
.isVisible('*[data-id="treeViewDivDraggableItemsrc/main.nr"]')
.click('*[data-id="treeViewDivDraggableItemsrc/main.nr"]')
.waitForElementVisible('*[data-id="compile-action"]')
},
'Create OpenZeppelin ERC20 template with Contract Wizard': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesSelect"]')
.pause(3000)
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="contract-wizard-topcard"]')
.click('*[data-id="contract-wizard-topcard"]')
.waitForElementVisible('*[data-id="contract-wizard-container"]')
.waitForElementVisible('*[data-id="contract-wizard-edit-icon"]')
.click('*[data-id="contract-wizard-edit-icon"]')
.waitForElementVisible('*[data-id="contract-wizard-token-name-input"]')
.setValue('*[data-id="contract-wizard-token-name-input"]', 'TestToken')
.click('*[data-id="contract-wizard-edit-icon"]')
.assert.textContains('*[data-id="contract-wizard-token-name-span"]', 'TestToken', 'Token name is correct')
.waitForElementVisible('*[data-id="contract-wizard-token-TestToken-input"]')
.assert.attributeMatches('#contractWizardTokenNameReadOnlyInput', 'data-id', 'contract-wizard-token-TestToken-input', 'Token name is correct')
.click('*[data-id="contract-wizard-mintable-checkbox"]')
.click('*[data-id="contract-wizard-burnable-checkbox"]')
.click('*[data-id="contract-wizard-pausable-checkbox"]')
.assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked')
.click('*[data-id="contract-wizard-validate-workspace-button"]')
.waitForElementVisible('*[data-id="finalize-contractWizard-workspace-edit-icon"]')
.click('*[data-id="finalize-contractWizard-workspace-edit-icon"]')
.waitForElementVisible('*[data-id="finalize-contract-wizard-workspaceName-input"]')
.setValue('*[data-id="finalize-contract-wizard-workspaceName-input"]', 'Test ERC20 Workspace')
.click('*[data-id="finalize-contractWizard-workspace-edit-icon"]')
.assert.textContains('*[data-id="finalize-contract-wizard-workspaceName-span"]', 'Test ERC20 Workspace', 'Workspace name is correct')
.click('*[data-id="validateWorkspaceButton"]')
.assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test ERC20 Workspace', 'Workspace name is correct')
.isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.isVisible('*[data-id="treeViewLitreeViewItemcontracts/TestToken.sol"]')
.click('*[data-id="treeViewLitreeViewItemcontracts/TestToken.sol"]')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract TestToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable {`) !== -1,
'Incorrect content')
})
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.click('*[data-id="compile-action"]')
.waitForElementVisible('#verticalIconsKindsolidity > i.remixui_status.fas.fa-check-circle.text-success.remixui_statusCheck')
.pause(2000)
},
'Create OpenZeppelin ERC721 template with Contract Wizard': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="workspacesSelect"]')
.pause(3000)
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="contract-wizard-topcard"]')
.click('*[data-id="contract-wizard-topcard"]')
.waitForElementVisible('*[data-id="contract-wizard-container"]')
.waitForElementVisible('*[data-id="contract-wizard-edit-icon"]')
.click('*[data-id="contract-wizard-edit-icon"]')
.waitForElementVisible('*[data-id="contract-wizard-token-name-input"]')
.setValue('*[data-id="contract-wizard-token-name-input"]', 'Test721Token')
.click('*[data-id="contract-wizard-edit-icon"]')
.assert.textContains('*[data-id="contract-wizard-token-name-span"]', 'Test721Token', 'Token name is correct')
.waitForElementVisible('*[data-id="contract-wizard-token-Test721Token-input"]')
.assert.attributeMatches('#contractWizardTokenNameReadOnlyInput', 'data-id', 'contract-wizard-token-Test721Token-input', 'Token name is correct')
.setValue('#contractWizardContractTagSelect', 'erc721')
// .click('*[data-id="contract-wizard-contract-tag-select-option-ERC721"]')
.click('*[data-id="contract-wizard-mintable-checkbox"]')
.click('*[data-id="contract-wizard-burnable-checkbox"]')
.click('*[data-id="contract-wizard-pausable-checkbox"]')
.assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked')
.click('*[data-id="contract-wizard-validate-workspace-button"]')
.waitForElementVisible('*[data-id="finalize-contractWizard-workspace-edit-icon"]')
.click('*[data-id="finalize-contractWizard-workspace-edit-icon"]')
.waitForElementVisible('*[data-id="finalize-contract-wizard-workspaceName-input"]')
.setValue('*[data-id="finalize-contract-wizard-workspaceName-input"]', 'Test ERC721 Workspace')
.click('*[data-id="finalize-contractWizard-workspace-edit-icon"]')
.assert.textContains('*[data-id="finalize-contract-wizard-workspaceName-span"]', 'Test ERC721 Workspace', 'Workspace name is correct')
.click('*[data-id="validateWorkspaceButton"]')
.assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test ERC721 Workspace', 'Workspace name is correct')
.pause(2000)
// .clickLaunchIcon('filePanel')
.isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.isVisible('*[data-id="treeViewLitreeViewItemcontracts/Test721Token.sol"]')
.click('*[data-id="treeViewLitreeViewItemcontracts/Test721Token.sol"]')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract Test721Token is ERC721, ERC721Pausable, Ownable, ERC721Burnable {`) !== -1,
'Incorrect content')
})
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.click('*[data-id="compile-action"]')
.waitForElementVisible('#verticalIconsKindsolidity > i.remixui_status.fas.fa-check-circle.text-success.remixui_statusCheck')
.clickLaunchIcon('solidity')
.isVisible('*[data-id="compilation-details"]')
}
}
2 changes: 2 additions & 0 deletions apps/remix-ide-e2e/src/tests/templates.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
'use strict'

import { NightwatchBrowser } from 'nightwatch'
Expand Down Expand Up @@ -231,6 +232,7 @@ function testTemplateOptions(browser: NightwatchBrowser, mode: 'create' | 'add')
})
}

//@ts-ignore
const tests = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
Expand Down
11 changes: 9 additions & 2 deletions apps/remix-ide/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import Terminal from './app/panels/terminal'
import TabProxy from './app/panels/tab-proxy.js'
import { Plugin } from '@remixproject/engine'
import BottomBarPanel from './app/components/bottom-bar-panel'
import { TemplateExplorerModalPlugin } from './app/plugins/remix-template-explorer-modal'

// Tracking now handled by this.track() method using MatomoManager

Expand Down Expand Up @@ -158,6 +159,7 @@ class AppComponent {
popupPanel: PopupPanel
statusBar: StatusBar
topBar: Topbar
templateExplorerModal: TemplateExplorerModalPlugin
settings: SettingsTab
params: any
desktopClientMode: boolean
Expand Down Expand Up @@ -267,6 +269,7 @@ class AppComponent {
this.gistHandler = new GistHandler()
// ----------------- theme service ---------------------------------
this.themeModule = new ThemeModule()
this.templateExplorerModal = new TemplateExplorerModalPlugin(this.themeModule)
// ----------------- locale service ---------------------------------
this.localeModule = new LocaleModule()
Registry.getInstance().put({ api: this.themeModule, name: 'themeModule' })
Expand Down Expand Up @@ -401,6 +404,8 @@ class AppComponent {

const templateSelection = new TemplatesSelectionPlugin()

const templateExplorerModal = this.templateExplorerModal

const walletConnect = new WalletConnect()

this.engine.register([
Expand Down Expand Up @@ -529,7 +534,7 @@ class AppComponent {

const bottomBarPanel = new BottomBarPanel()

this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, this.statusBar, this.topBar, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel, this.popupPanel, bottomBarPanel])
this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, this.statusBar, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel, this.popupPanel, bottomBarPanel])

// CONTENT VIEWS & DEFAULT PLUGINS
const openZeppelinProxy = new OpenZeppelinProxy(blockchain)
Expand Down Expand Up @@ -571,6 +576,7 @@ class AppComponent {
openZeppelinProxy,
run.recorder
])
this.engine.register([templateExplorerModal, this.topBar])

this.layout.panels = {
tabs: { plugin: tabProxy, active: true },
Expand Down Expand Up @@ -609,8 +615,9 @@ class AppComponent {
])

await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs'])
await this.appManager.activatePlugin(['topbar'])
await this.appManager.activatePlugin(['topbar', 'templateexplorermodal'])
await this.appManager.activatePlugin(['statusBar'])
// await this.appManager.activatePlugin(['remix-template-explorer-modal'])
await this.appManager.activatePlugin(['bottomBar'])
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['pinnedPanel'])
Expand Down
1 change: 1 addition & 0 deletions apps/remix-ide/src/app/components/status-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export class StatusBar extends Plugin<any, CustomRemixApi> implements StatusBarI
this.on('settings', 'copilotChoiceChanged', (isAiActive: boolean) => {
this.isAiActive = isAiActive
})

this.renderComponent()
}

Expand Down
1 change: 1 addition & 0 deletions apps/remix-ide/src/app/components/top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const TopBarProfile = {
description: '',
version: packageJson.version,
icon: '',
location: 'none',
methods: ['getWorkspaces', 'createWorkspace', 'renameWorkspace', 'deleteWorkspace', 'getCurrentWorkspace', 'setWorkspace'],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
}
Expand Down
2 changes: 2 additions & 0 deletions apps/remix-ide/src/app/plugins/notification.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @nrwl/nx/enforce-module-boundaries */
import { Plugin } from '@remixproject/engine'
import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils'
import { AppModal } from '@remix-ui/app'
Expand All @@ -10,6 +11,7 @@ interface INotificationApi {
modal: (args: AppModal) => void
alert: (args: AlertModal) => void
toast: (message: string) => void

}
}

Expand Down
83 changes: 83 additions & 0 deletions apps/remix-ide/src/app/plugins/remix-template-explorer-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable @nrwl/nx/enforce-module-boundaries */
import React from 'react'
import { AppAction, AppState } from '@remix-ui/app'
import { PluginViewWrapper } from '@remix-ui/helper'
import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events'
import { ThemeModule } from '../tabs/theme-module'
import * as packageJson from '../../../../../package.json'
import { TemplateExplorerProvider } from 'libs/remix-ui/template-explorer-modal/context/template-explorer-context'
import { ViewPlugin } from '@remixproject/engine-web'

const pluginProfile = {
name: 'templateexplorermodal',
displayName: 'Template Explorer Modal',
description: 'Template Explorer Modal',
methods: ['openModal'],
events: [],
maintainedBy: 'Remix',
kind: 'templateexplorermodal',
location: 'none',
version: packageJson.version,
permission: true,
documentation: 'https://remix-ide.readthedocs.io/en/latest/template-explorer-modal.html'
}

export class TemplateExplorerModalPlugin extends Plugin {
element: HTMLDivElement
dispatch: React.Dispatch<any> = () => { }
event: any
appStateDispatch: any
theme: any = null
constructor(theme: ThemeModule) {
super(pluginProfile)
this.element = document.createElement('div')
this.element.setAttribute('id', 'template-explorer-modal')
this.dispatch = () => { }
this.event = new EventEmitter()
this.theme = theme
}

async onActivation(): Promise<void> {
this.on('theme', 'themeChanged', (theme: any) => {
this.theme = theme
})
}

openModal() {
console.log('This is openModal')
}

onDeactivation(): void {

}

setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
this.renderComponent()
}

setAppStateDispatch(appStateDispatch: React.Dispatch<AppAction>) {
this.appStateDispatch = appStateDispatch
}

render() {
return (
<div id="inner-remix-template-explorer-modal">
<PluginViewWrapper plugin={this} useAppContext={true} />
</div>
)
}

renderComponent(): void {
this.dispatch({
...this
})
}

updateComponent(state: any) {
return (
<TemplateExplorerProvider plugin={state} />
)
}
}
8 changes: 7 additions & 1 deletion apps/remix-ide/src/app/plugins/remix-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const profile = {
name: 'remix-templates',
displayName: 'remix-templates',
description: 'Remix Templates plugin',
methods: ['getTemplate', 'loadTemplateInNewWindow', 'addToCurrentElectronFolder', 'loadFilesInNewWindow'],
methods: ['getTemplate', 'loadTemplateInNewWindow', 'addToCurrentElectronFolder', 'loadFilesInNewWindow', 'getTemplateReadMeFile'],
}

export class TemplatesPlugin extends Plugin {
Expand All @@ -27,6 +27,12 @@ export class TemplatesPlugin extends Plugin {
const files = await templateWithContent[template](opts, this)
return files
}

async getTemplateReadMeFile(templateName: string) {
const files = await templateWithContent[templateName]({}, this)
const readMe = files['README.md'] || files['README.txt'] || 'No ReadMe file found'
return { readMe, type: files['README.md'] ? 'md' : files['README.txt'] ? 'txt' : 'none' }
}
// electron only method

async addToCurrentElectronFolder(template: string, opts?: any) {
Expand Down
Loading