diff --git a/apps/remix-ide-e2e/src/tests/script-runner.test.ts b/apps/remix-ide-e2e/src/tests/script-runner.test.ts new file mode 100644 index 0000000000..493466983d --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/script-runner.test.ts @@ -0,0 +1,116 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', false) + }, + 'Should load default script runner': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('scriptRunnerBridge') + .waitForElementVisible('[data-id="sr-loaded-default"]') + .waitForElementVisible('[data-id="dependency-ethers-^5"]') + .waitForElementVisible('[data-id="sr-toggle-ethers6"]') + }, + 'Should load script runner ethers6': function (browser: NightwatchBrowser) { + browser + .click('[data-id="sr-toggle-ethers6"]') + .waitForElementVisible('[data-id="sr-loaded-ethers6"]') + .waitForElementPresent('[data-id="dependency-ethers-^6"]') + }, + 'Should have config file in .remix/script.config.json': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .waitForElementVisible('[data-path=".remix"]') + .waitForElementVisible('[data-id="treeViewDivDraggableItem.remix/script.config.json"]') + .openFile('.remix/script.config.json') + }, + 'check config file content': function (browser: NightwatchBrowser) { + browser + .getEditorValue((content) => { + console.log(JSON.parse(content)) + const parsed = JSON.parse(content) + browser.assert.ok(parsed.defaultConfig === 'ethers6', 'config file content is correct') + }) + }, + 'execute ethers6 script': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="treeViewUltreeViewMenu"]') // make sure we create the file at the root folder + .addFile('deployWithEthersJs.js', { content: deployWithEthersJs }) + .pause(1000) + .click('[data-id="treeViewDivtreeViewItemcontracts"]') + .openFile('contracts/2_Owner.sol') + .clickLaunchIcon('solidity') + .click('*[data-id="compilerContainerCompileBtn"]') + .executeScriptInTerminal('remix.execute(\'deployWithEthersJs.js\')') + .waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000) + }, + 'switch workspace it should be default again': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .pause(2000) + .waitForElementVisible('*[data-id="workspacesMenuDropdown"]') + .click('*[data-id="workspacesMenuDropdown"]') + .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-semaphore"]') + .scrollAndClick('*[data-id="create-semaphore"]') + .modalFooterOKClick('TemplatesSelection') + .clickLaunchIcon('scriptRunnerBridge') + .waitForElementVisible('[data-id="sr-loaded-default"]') + .waitForElementVisible('[data-id="dependency-ethers-^5"]') + .waitForElementVisible('[data-id="sr-toggle-ethers6"]') + }, + 'switch to default workspace that should be on ethers6': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .switchWorkspace('default_workspace') + .clickLaunchIcon('scriptRunnerBridge') + .waitForElementVisible('[data-id="sr-loaded-ethers6"]') + .waitForElementPresent('[data-id="dependency-ethers-^6"]') + } +} + + +const deployWithEthersJs = ` +import { ethers } from 'ethers' + +/** + * Deploy the given contract + * @param {string} contractName name of the contract to deploy + * @param {Array} args list of constructor' parameters + * @param {Number} accountIndex account index from the exposed account + * @return {Contract} deployed contract + * + */ +const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => { + + console.log(\`deploying \${contractName}\`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = \`contracts/artifacts/\${contractName}.json\` // Change this for different path + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + // 'web3Provider' is a remix global variable object + + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + + const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) + + const contract = await factory.deploy(...args) + + // The contract is NOT deployed yet; we must wait until it is mined + await contract.waitForDeployment() + return contract +} + +(async () => { + try { + const contract = await deploy('Owner', []) + + console.log(\`address: \${await contract.getAddress()}\`) + } catch (e) { + console.log(e.message) + } +})()` \ No newline at end of file diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 796d354c5c..a936cc5e33 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -77,6 +77,7 @@ const remixLib = require('@remix-project/remix-lib') import { QueryParams } from '@remix-project/remix-lib' import { SearchPlugin } from './app/tabs/search' +import { ScriptRunnerUIPlugin } from './app/tabs/script-runner-ui' import { ElectronProvider } from './app/files/electronProvider' const Storage = remixLib.Storage @@ -246,6 +247,9 @@ class AppComponent { //----- search const search = new SearchPlugin() + //---------------- Script Runner UI Plugin ------------------------- + const scriptRunnerUI = new ScriptRunnerUIPlugin(this.engine) + //---- templates const templates = new TemplatesPlugin() @@ -396,6 +400,7 @@ class AppComponent { pluginStateLogger, matomo, templateSelection, + scriptRunnerUI, remixAI ]) @@ -649,7 +654,7 @@ class AppComponent { }) // activate solidity plugin - this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy']) + this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy', 'scriptRunnerBridge']) } } diff --git a/apps/remix-ide/src/app/components/hidden-panel.tsx b/apps/remix-ide/src/app/components/hidden-panel.tsx index 27993313c0..0629d2014f 100644 --- a/apps/remix-ide/src/app/components/hidden-panel.tsx +++ b/apps/remix-ide/src/app/components/hidden-panel.tsx @@ -24,10 +24,16 @@ export class HiddenPanel extends AbstractPanel { addView(profile: any, view: any): void { super.removeView(profile) + this.renderComponent() super.addView(profile, view) this.renderComponent() } + removeView(profile: any): void { + super.removeView(profile) + this.renderComponent() + } + updateComponent(state: any) { return } plugins={state.plugins} /> } diff --git a/apps/remix-ide/src/app/panels/terminal.tsx b/apps/remix-ide/src/app/panels/terminal.tsx index 2cb89e3fcb..a30cf085bd 100644 --- a/apps/remix-ide/src/app/panels/terminal.tsx +++ b/apps/remix-ide/src/app/panels/terminal.tsx @@ -117,10 +117,10 @@ class Terminal extends Plugin { } onDeactivation() { - this.off('scriptRunner', 'log') - this.off('scriptRunner', 'info') - this.off('scriptRunner', 'warn') - this.off('scriptRunner', 'error') + this.off('scriptRunnerBridge', 'log') + this.off('scriptRunnerBridge', 'info') + this.off('scriptRunnerBridge', 'warn') + this.off('scriptRunnerBridge', 'error') } logHtml(html) { diff --git a/apps/remix-ide/src/app/plugins/matomo.ts b/apps/remix-ide/src/app/plugins/matomo.ts index e6af25655c..0421ec6a49 100644 --- a/apps/remix-ide/src/app/plugins/matomo.ts +++ b/apps/remix-ide/src/app/plugins/matomo.ts @@ -11,7 +11,7 @@ const profile = { version: '1.0.0' } -const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'dgit', 'contract-verification'] +const allowedPlugins = ['LearnEth', 'etherscan', 'vyper', 'circuit-compiler', 'doc-gen', 'doc-viewer', 'solhint', 'walletconnect', 'scriptRunner', 'scriptRunnerBridge', 'dgit', 'contract-verification'] export class Matomo extends Plugin { diff --git a/apps/remix-ide/src/app/tabs/compile-and-run.ts b/apps/remix-ide/src/app/tabs/compile-and-run.ts index 63952747c9..9643cd78e9 100644 --- a/apps/remix-ide/src/app/tabs/compile-and-run.ts +++ b/apps/remix-ide/src/app/tabs/compile-and-run.ts @@ -62,7 +62,7 @@ export class CompileAndRun extends Plugin { if (clearAllInstances) { await this.call('udapp', 'clearAllInstances') } - await this.call('scriptRunner', 'execute', content, fileName) + await this.call('scriptRunnerBridge', 'execute', content, fileName) } catch (e) { this.call('notification', 'toast', e.message || e) } diff --git a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json index b3f77de301..5870ea16e0 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json +++ b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json @@ -7,6 +7,7 @@ "remixUiTabs.tooltipText6": "Enable RemixAI Copilot [BETA]", "remixUiTabs.tooltipText7": "Disable RemixAI Copilot [BETA]", "remixUiTabs.tooltipText8": "Remix AI Tools Documentation", + "remixUiTabs.tooltipText9": "Configure scripting dependencies", "remixUiTabs.tooltipTextDisabledCopilot": "To use RemixAI Copilot, choose a .sol file", "remixUiTabs.zoomOut": "Zoom out", "remixUiTabs.zoomIn": "Zoom in" diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx new file mode 100644 index 0000000000..7e108c71ca --- /dev/null +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -0,0 +1,399 @@ +import { IframePlugin, IframeProfile, ViewPlugin } from '@remixproject/engine-web' +import * as packageJson from '../../../../../package.json' +import React from 'react' // eslint-disable-line +import { customScriptRunnerConfig, ProjectConfiguration, ScriptRunnerConfig, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line +import { Profile } from '@remixproject/plugin-utils' +import { Engine, Plugin } from '@remixproject/engine' +import axios from 'axios' +import { AppModal } from '@remix-ui/app' +import { isArray } from 'lodash' +import { PluginViewWrapper } from '@remix-ui/helper' +import { CustomRemixApi } from '@remix-api' + +const profile = { + name: 'scriptRunnerBridge', + displayName: 'Script configuration', + methods: ['execute'], + events: ['log', 'info', 'warn', 'error'], + icon: 'assets/img/solid-gear-circle-play.svg', + description: 'Configure the dependencies for running scripts.', + kind: '', + location: 'sidePanel', + version: packageJson.version, + maintainedBy: 'Remix' +} + +const configFileName = '.remix/script.config.json' + +let baseUrl = 'https://remix-project-org.github.io/script-runner-generator' +const customBuildUrl = 'http://localhost:4000/build' // this will be used when the server is ready + +interface IScriptRunnerState { + customConfig: customScriptRunnerConfig + configurations: ProjectConfiguration[] + activeConfig: ProjectConfiguration + enableCustomScriptRunner: boolean +} + +export class ScriptRunnerUIPlugin extends ViewPlugin { + engine: Engine + dispatch: React.Dispatch = () => { } + workspaceScriptRunnerDefaults: Record + customConfig: ScriptRunnerConfig + configurations: ProjectConfiguration[] + activeConfig: ProjectConfiguration + enableCustomScriptRunner: boolean + plugin: Plugin + scriptRunnerProfileName: string + constructor(engine: Engine) { + super(profile) + this.engine = engine + this.workspaceScriptRunnerDefaults = {} + this.plugin = this + this.enableCustomScriptRunner = false // implement this later + } + + async onActivation() { + + this.on('filePanel', 'setWorkspace', async (workspace: string) => { + this.activeConfig = null + this.customConfig = + { + defaultConfig: 'default', + customConfig: { + baseConfiguration: 'default', + dependencies: [] + } + } + await this.loadCustomConfig() + await this.loadConfigurations() + this.renderComponent() + }) + + this.plugin.on('fileManager', 'fileSaved', async (file: string) => { + + if (file === configFileName && this.enableCustomScriptRunner) { + await this.loadCustomConfig() + this.renderComponent() + } + }) + await this.loadCustomConfig() + await this.loadConfigurations() + this.renderComponent() + } + + render() { + return ( +
+ +
+ ) + } + + setDispatch(dispatch: React.Dispatch) { + this.dispatch = dispatch + this.renderComponent() + } + + renderComponent() { + this.dispatch({ + customConfig: this.customConfig, + configurations: this.configurations, + activeConfig: this.activeConfig, + enableCustomScriptRunner: this.enableCustomScriptRunner + }) + } + + updateComponent(state: IScriptRunnerState) { + return ( + + ) + } + + async selectScriptRunner(config: ProjectConfiguration) { + if (await this.loadScriptRunner(config)) + await this.saveCustomConfig(this.customConfig) + } + + async loadScriptRunner(config: ProjectConfiguration): Promise { + + const profile: Profile = await this.plugin.call('manager', 'getProfile', 'scriptRunner') + this.scriptRunnerProfileName = profile.name + const testPluginName = localStorage.getItem('test-plugin-name') + const testPluginUrl = localStorage.getItem('test-plugin-url') + + let url = `${baseUrl}?template=${config.name}×tamp=${Date.now()}` + if (testPluginName === 'scriptRunner') { + // if testpluginurl has template specified only use that + if (testPluginUrl.indexOf('template') > -1) { + url = testPluginUrl + } else { + baseUrl = `//${new URL(testPluginUrl).host}` + url = `${baseUrl}?template=${config.name}×tamp=${Date.now()}` + } + } + //console.log('loadScriptRunner', profile) + const newProfile: IframeProfile = { + ...profile, + name: profile.name + config.name, + location: 'hiddenPanel', + url: url + } + + let result = null + try { + this.setIsLoading(config.name, true) + const plugin: IframePlugin = new IframePlugin(newProfile) + if (!this.engine.isRegistered(newProfile.name)) { + + await this.engine.register(plugin) + } + await this.plugin.call('manager', 'activatePlugin', newProfile.name) + + this.activeConfig = config + this.on(newProfile.name, 'log', this.log.bind(this)) + this.on(newProfile.name, 'info', this.info.bind(this)) + this.on(newProfile.name, 'warn', this.warn.bind(this)) + this.on(newProfile.name, 'error', this.error.bind(this)) + this.on(newProfile.name, 'dependencyError', this.dependencyError.bind(this)) + this.customConfig.defaultConfig = config.name + this.setErrorStatus(config.name, false, '') + result = true + } catch (e) { + console.log('Error loading script runner: ', newProfile.name, e) + const iframe = document.getElementById(`plugin-${newProfile.name}`); + if (iframe) { + await this.call('hiddenPanel', 'removeView', newProfile) + } + + delete (this.engine as any).manager.profiles[newProfile.name] + delete (this.engine as any).plugins[newProfile.name] + console.log('Error loading script runner: ', newProfile.name, e) + this.setErrorStatus(config.name, true, e) + result = false + } + + this.setIsLoading(config.name, false) + this.renderComponent() + return result + + } + + async execute(script: string, filePath: string) { + this.call('terminal', 'log', { value: `running ${filePath} ...`, type: 'info' }) + if (!this.scriptRunnerProfileName || !this.engine.isRegistered(`${this.scriptRunnerProfileName}${this.activeConfig.name}`)) { + if (!await this.loadScriptRunner(this.activeConfig)) { + console.error('Error loading script runner') + return + } + } + try { + this.setIsLoading(this.activeConfig.name, true) + await this.call(`${this.scriptRunnerProfileName}${this.activeConfig.name}`, 'execute', script, filePath) + } catch (e) { + console.error('Error executing script', e) + } + this.setIsLoading(this.activeConfig.name, false) + + } + + async setErrorStatus(name: string, status: boolean, error: string) { + this.configurations.forEach((config) => { + if (config.name === name) { + config.errorStatus = status + config.error = error + } + }) + this.renderComponent() + } + + async setIsLoading(name: string, status: boolean) { + if (status) { + this.emit('statusChanged', { + key: 'loading', + type: 'info', + title: 'loading...' + }) + } else { + this.emit('statusChanged', { + key: 'none' + }) + } + this.configurations.forEach((config) => { + if (config.name === name) { + config.isLoading = status + } + }) + this.renderComponent() + } + + async dependencyError(data: any) { + console.log('Script runner dependency error: ', data) + let message = `Error loading dependencies: ` + if (isArray(data.data)) { + data.data.forEach((data: any) => { + message += `${data}` + }) + } + + const modal: AppModal = { + id: 'TemplatesSelection', + title: 'Missing dependencies', + message: `${message} \n\n You may need to setup a script engine for this workspace to load the correct dependencies. Do you want go to setup now?`, + okLabel: window._intl.formatMessage({ id: 'filePanel.ok' }), + cancelLabel: 'ignore' + } + const modalResult = await this.plugin.call('notification' as any, 'modal', modal) + if (modalResult) { + await this.plugin.call('menuicons', 'select', 'scriptRunnerBridge') + } else { + + } + } + + async log(data: any) { + this.emit('log', data) + } + + async warn(data: any) { + this.emit('warn', data) + } + + async error(data: any) { + this.emit('error', data) + } + + async info(data: any) { + this.emit('info', data) + } + + async loadCustomConfig(): Promise { + try { + const content = await this.plugin.call('fileManager', 'readFile', configFileName) + const parsed = JSON.parse(content) + this.customConfig = parsed + } catch (e) { + this.customConfig = { + defaultConfig: 'default', + customConfig: { + baseConfiguration: 'default', + dependencies: [] + } + } + } + + } + + async openCustomConfig() { + try { + await this.plugin.call('fileManager', 'open', '.remix/script.config.json') + } catch (e) { + + } + } + + async loadConfigurations() { + try { + const response = await axios.get(`${baseUrl}/projects.json?timestamp=${Date.now()}`); + this.configurations = response.data; + // find the default otherwise pick the first one as the active + this.configurations.forEach((config) => { + if (config.name === (this.customConfig.defaultConfig)) { + this.activeConfig = config; + } + }); + if (!this.activeConfig) { + this.activeConfig = this.configurations[0]; + } + } catch (error) { + console.error("Error fetching the projects data:", error); + } + + } + + async saveCustomConfig(content: ScriptRunnerConfig) { + if (content.customConfig.dependencies.length === 0 && content.defaultConfig === 'default') { + try { + const exists = await this.plugin.call('fileManager', 'exists', '.remix/script.config.json') + if (exists) { + await this.plugin.call('fileManager', 'remove', '.remix/script.config.json') + } + } catch (e) { + } + return + } + await this.plugin.call('fileManager', 'writeFile', '.remix/script.config.json', JSON.stringify(content, null, 2)) + } + + async activateCustomScriptRunner(config: customScriptRunnerConfig) { + try { + const result = await axios.post(customBuildUrl, config) + if (result.data.hash) { + + const newConfig: ProjectConfiguration = { + name: result.data.hash, + title: 'Custom configuration', + publish: true, + description: `Extension of ${config.baseConfiguration}`, + dependencies: config.dependencies, + replacements: {}, + errorStatus: false, + error: '', + isLoading: false + }; + this.configurations.push(newConfig) + this.renderComponent() + await this.loadScriptRunner(result.data.hash) + } + return result.data.hash + } catch (error) { + let message + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log('Error status:', error.response.status); + console.log('Error data:', error.response.data); // This should give you the output being sent + console.log('Error headers:', error.response.headers); + + if (error.response.data.error) { + + if (isArray(error.response.data.error)) { + const message = `${error.response.data.error[0]}` + this.plugin.call('notification', 'alert', { + id: 'scriptalert', + message, + title: 'Error' + }) + throw new Error(message) + } + message = `${error.response.data.error}` + } + message = `Uknown error: ${error.response.data}` + this.plugin.call('notification', 'alert', { + id: 'scriptalert', + message, + title: 'Error' + }) + throw new Error(message) + } else if (error.request) { + // The request was made but no response was received + console.log('No response received:', error.request); + throw new Error('No response received') + } else { + // Something happened in setting up the request that triggered an Error + console.log('Error message:', error.message); + throw new Error(error.message) + } + + } + } + +} \ No newline at end of file diff --git a/apps/remix-ide/src/assets/fontawesome/css/all.css b/apps/remix-ide/src/assets/fontawesome/css/all.css index 59c32c20ed..8b1fd3fad7 100644 --- a/apps/remix-ide/src/assets/fontawesome/css/all.css +++ b/apps/remix-ide/src/assets/fontawesome/css/all.css @@ -2,26 +2,28 @@ font-family: var(--fa-style-family, "Font Awesome 6 Pro"); font-weight: var(--fa-style, 900); } -.fa, -.fa-classic, -.fa-sharp, -.fas, .fa-solid, -.far, .fa-regular, -.fasr, +.fa-brands, +.fas, +.far, +.fab, .fal, -.fa-light, -.fasl, .fat, -.fa-thin, -.fast, .fad, -.fa-duotone, .fass, +.fasr, +.fasl, +.fast, +.fasds, +.fa-light, +.fa-thin, +.fa-duotone, +.fa-sharp, +.fa-sharp-duotone, .fa-sharp-solid, -.fab, -.fa-brands { +.fa-classic, +.fa { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; display: var(--fa-display, inline-block); @@ -31,14 +33,14 @@ text-rendering: auto; } .fas, -.fa-classic, -.fa-solid, .far, -.fa-regular, .fal, -.fa-light, .fat, -.fa-thin { +.fa-solid, +.fa-regular, +.fa-light, +.fa-thin, +.fa-classic { font-family: 'Font Awesome 6 Pro'; } .fab, @@ -50,6 +52,14 @@ .fa-duotone { font-family: 'Font Awesome 6 Duotone'; } +.fasds, +.fa-sharp-duotone { + font-family: 'Font Awesome 6 Sharp Duotone'; } + +.fasds, +.fa-sharp-duotone { + font-weight: 900; } + .fass, .fasr, .fasl, @@ -133,7 +143,7 @@ position: relative; } .fa-li { - left: calc(var(--fa-li-width, 2em) * -1); + left: calc(-1 * var(--fa-li-width, 2em)); position: absolute; text-align: center; width: var(--fa-li-width, 2em); @@ -155,118 +165,71 @@ margin-left: var(--fa-pull-margin, 0.3em); } .fa-beat { - -webkit-animation-name: fa-beat; - animation-name: fa-beat; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); - animation-timing-function: var(--fa-animation-timing, ease-in-out); } + animation-name: fa-beat; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-bounce { - -webkit-animation-name: fa-bounce; - animation-name: fa-bounce; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); - animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } + animation-name: fa-bounce; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } .fa-fade { - -webkit-animation-name: fa-fade; - animation-name: fa-fade; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); - animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + animation-name: fa-fade; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } .fa-beat-fade { - -webkit-animation-name: fa-beat-fade; - animation-name: fa-beat-fade; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); - animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + animation-name: fa-beat-fade; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } .fa-flip { - -webkit-animation-name: fa-flip; - animation-name: fa-flip; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); - animation-timing-function: var(--fa-animation-timing, ease-in-out); } + animation-name: fa-flip; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-shake { - -webkit-animation-name: fa-shake; - animation-name: fa-shake; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, linear); - animation-timing-function: var(--fa-animation-timing, linear); } + animation-name: fa-shake; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin { - -webkit-animation-name: fa-spin; - animation-name: fa-spin; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 2s); - animation-duration: var(--fa-animation-duration, 2s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, linear); - animation-timing-function: var(--fa-animation-timing, linear); } + animation-name: fa-spin; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 2s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-reverse { --fa-animation-direction: reverse; } .fa-pulse, .fa-spin-pulse { - -webkit-animation-name: fa-spin; - animation-name: fa-spin; - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, steps(8)); - animation-timing-function: var(--fa-animation-timing, steps(8)); } + animation-name: fa-spin; + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, steps(8)); } @media (prefers-reduced-motion: reduce) { .fa-beat, @@ -278,219 +241,97 @@ .fa-shake, .fa-spin, .fa-spin-pulse { - -webkit-animation-delay: -1ms; - animation-delay: -1ms; - -webkit-animation-duration: 1ms; - animation-duration: 1ms; - -webkit-animation-iteration-count: 1; - animation-iteration-count: 1; - -webkit-transition-delay: 0s; - transition-delay: 0s; - -webkit-transition-duration: 0s; - transition-duration: 0s; } } - -@-webkit-keyframes fa-beat { - 0%, 90% { - -webkit-transform: scale(1); - transform: scale(1); } - 45% { - -webkit-transform: scale(var(--fa-beat-scale, 1.25)); - transform: scale(var(--fa-beat-scale, 1.25)); } } + animation-delay: -1ms; + animation-duration: 1ms; + animation-iteration-count: 1; + transition-delay: 0s; + transition-duration: 0s; } } @keyframes fa-beat { 0%, 90% { - -webkit-transform: scale(1); - transform: scale(1); } + transform: scale(1); } 45% { - -webkit-transform: scale(var(--fa-beat-scale, 1.25)); - transform: scale(var(--fa-beat-scale, 1.25)); } } - -@-webkit-keyframes fa-bounce { - 0% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } - 10% { - -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); - transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } - 30% { - -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); - transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } - 50% { - -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); - transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } - 57% { - -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); - transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } - 64% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } - 100% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } } + transform: scale(var(--fa-beat-scale, 1.25)); } } @keyframes fa-bounce { 0% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } + transform: scale(1, 1) translateY(0); } 10% { - -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); - transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } 30% { - -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); - transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } 50% { - -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); - transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } 57% { - -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); - transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } 64% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } + transform: scale(1, 1) translateY(0); } 100% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } } - -@-webkit-keyframes fa-fade { - 50% { - opacity: var(--fa-fade-opacity, 0.4); } } + transform: scale(1, 1) translateY(0); } } @keyframes fa-fade { 50% { opacity: var(--fa-fade-opacity, 0.4); } } -@-webkit-keyframes fa-beat-fade { - 0%, 100% { - opacity: var(--fa-beat-fade-opacity, 0.4); - -webkit-transform: scale(1); - transform: scale(1); } - 50% { - opacity: 1; - -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); - transform: scale(var(--fa-beat-fade-scale, 1.125)); } } - @keyframes fa-beat-fade { 0%, 100% { opacity: var(--fa-beat-fade-opacity, 0.4); - -webkit-transform: scale(1); - transform: scale(1); } + transform: scale(1); } 50% { opacity: 1; - -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); - transform: scale(var(--fa-beat-fade-scale, 1.125)); } } - -@-webkit-keyframes fa-flip { - 50% { - -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); - transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } @keyframes fa-flip { 50% { - -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); - transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } - -@-webkit-keyframes fa-shake { - 0% { - -webkit-transform: rotate(-15deg); - transform: rotate(-15deg); } - 4% { - -webkit-transform: rotate(15deg); - transform: rotate(15deg); } - 8%, 24% { - -webkit-transform: rotate(-18deg); - transform: rotate(-18deg); } - 12%, 28% { - -webkit-transform: rotate(18deg); - transform: rotate(18deg); } - 16% { - -webkit-transform: rotate(-22deg); - transform: rotate(-22deg); } - 20% { - -webkit-transform: rotate(22deg); - transform: rotate(22deg); } - 32% { - -webkit-transform: rotate(-12deg); - transform: rotate(-12deg); } - 36% { - -webkit-transform: rotate(12deg); - transform: rotate(12deg); } - 40%, 100% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } } + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } @keyframes fa-shake { 0% { - -webkit-transform: rotate(-15deg); - transform: rotate(-15deg); } + transform: rotate(-15deg); } 4% { - -webkit-transform: rotate(15deg); - transform: rotate(15deg); } + transform: rotate(15deg); } 8%, 24% { - -webkit-transform: rotate(-18deg); - transform: rotate(-18deg); } + transform: rotate(-18deg); } 12%, 28% { - -webkit-transform: rotate(18deg); - transform: rotate(18deg); } + transform: rotate(18deg); } 16% { - -webkit-transform: rotate(-22deg); - transform: rotate(-22deg); } + transform: rotate(-22deg); } 20% { - -webkit-transform: rotate(22deg); - transform: rotate(22deg); } + transform: rotate(22deg); } 32% { - -webkit-transform: rotate(-12deg); - transform: rotate(-12deg); } + transform: rotate(-12deg); } 36% { - -webkit-transform: rotate(12deg); - transform: rotate(12deg); } + transform: rotate(12deg); } 40%, 100% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } } - -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } + transform: rotate(0deg); } } @keyframes fa-spin { 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } + transform: rotate(0deg); } 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } + transform: rotate(360deg); } } .fa-rotate-90 { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); } + transform: rotate(90deg); } .fa-rotate-180 { - -webkit-transform: rotate(180deg); - transform: rotate(180deg); } + transform: rotate(180deg); } .fa-rotate-270 { - -webkit-transform: rotate(270deg); - transform: rotate(270deg); } + transform: rotate(270deg); } .fa-flip-horizontal { - -webkit-transform: scale(-1, 1); - transform: scale(-1, 1); } + transform: scale(-1, 1); } .fa-flip-vertical { - -webkit-transform: scale(1, -1); - transform: scale(1, -1); } + transform: scale(1, -1); } .fa-flip-both, .fa-flip-horizontal.fa-flip-vertical { - -webkit-transform: scale(-1, -1); - transform: scale(-1, -1); } + transform: scale(-1, -1); } .fa-rotate-by { - -webkit-transform: rotate(var(--fa-rotate-angle, 0)); - transform: rotate(var(--fa-rotate-angle, 0)); } + transform: rotate(var(--fa-rotate-angle, 0)); } .fa-stack { display: inline-block; @@ -1799,6 +1640,7 @@ readers do not read off random characters that represent icons */ .fa-diamond-half::before { content: "\e5b7"; } .fa-diamond-half-stroke::before { content: "\e5b8"; } .fa-diamond-turn-right::before { content: "\f5eb"; } +.fa-diamonds-4::before { content: "\e68b"; } .fa-dice::before { content: "\f522"; } .fa-dice-d10::before { content: "\f6cd"; } .fa-dice-d12::before { content: "\f6ce"; } @@ -2383,6 +2225,7 @@ readers do not read off random characters that represent icons */ .fa-globe-pointer::before { content: "\e60e"; } .fa-globe-snow::before { content: "\f7a3"; } .fa-globe-stand::before { content: "\f5f6"; } +.fa-globe-wifi::before { content: "\e685"; } .fa-glove-boxing::before { content: "\f438"; } .fa-goal-net::before { content: "\e3ab"; } .fa-golf-ball::before { content: "\f450"; } @@ -2687,6 +2530,7 @@ readers do not read off random characters that represent icons */ .fa-humidity::before { content: "\f750"; } .fa-hundred-points::before { content: "\e41c"; } .fa-hurricane::before { content: "\f751"; } +.fa-hydra::before { content: "\e686"; } .fa-hyphen::before { content: "\2d"; } .fa-i::before { content: "\49"; } .fa-i-cursor::before { content: "\f246"; } @@ -2854,6 +2698,7 @@ readers do not read off random characters that represent icons */ .fa-lightbulb-exclamation::before { content: "\f671"; } .fa-lightbulb-exclamation-on::before { content: "\e1ca"; } .fa-lightbulb-gear::before { content: "\e5fd"; } +.fa-lightbulb-message::before { content: "\e687"; } .fa-lightbulb-on::before { content: "\f672"; } .fa-lightbulb-slash::before { content: "\f673"; } .fa-lighthouse::before { content: "\e612"; } @@ -3208,6 +3053,7 @@ readers do not read off random characters that represent icons */ .fa-octagon-minus::before { content: "\f308"; } .fa-octagon-plus::before { content: "\f301"; } .fa-octagon-xmark::before { content: "\f2f0"; } +.fa-octopus::before { content: "\e688"; } .fa-oil-can::before { content: "\f613"; } .fa-oil-can-drip::before { content: "\e205"; } .fa-oil-temp::before { content: "\f614"; } @@ -4196,9 +4042,12 @@ readers do not read off random characters that represent icons */ .fa-table::before { content: "\f0ce"; } .fa-table-cells::before { content: "\f00a"; } .fa-table-cells-column-lock::before { content: "\e678"; } +.fa-table-cells-column-unlock::before { content: "\e690"; } .fa-table-cells-large::before { content: "\f009"; } .fa-table-cells-lock::before { content: "\e679"; } .fa-table-cells-row-lock::before { content: "\e67a"; } +.fa-table-cells-row-unlock::before { content: "\e691"; } +.fa-table-cells-unlock::before { content: "\e692"; } .fa-table-columns::before { content: "\f0db"; } .fa-table-layout::before { content: "\e290"; } .fa-table-list::before { content: "\f00b"; } @@ -4311,9 +4160,11 @@ readers do not read off random characters that represent icons */ .fa-theta::before { content: "\f69e"; } .fa-thought-bubble::before { content: "\e32e"; } .fa-thumb-tack::before { content: "\f08d"; } +.fa-thumb-tack-slash::before { content: "\e68f"; } .fa-thumbs-down::before { content: "\f165"; } .fa-thumbs-up::before { content: "\f164"; } .fa-thumbtack::before { content: "\f08d"; } +.fa-thumbtack-slash::before { content: "\e68f"; } .fa-thunderstorm::before { content: "\f76c"; } .fa-thunderstorm-moon::before { content: "\f76d"; } .fa-thunderstorm-sun::before { content: "\f76e"; } @@ -4556,6 +4407,7 @@ readers do not read off random characters that represent icons */ .fa-user-alt::before { content: "\f406"; } .fa-user-alt-slash::before { content: "\f4fa"; } .fa-user-astronaut::before { content: "\f4fb"; } +.fa-user-beard-bolt::before { content: "\e689"; } .fa-user-bounty-hunter::before { content: "\e2bf"; } .fa-user-chart::before { content: "\f6a3"; } .fa-user-check::before { content: "\f4fc"; } @@ -4584,6 +4436,7 @@ readers do not read off random characters that represent icons */ .fa-user-hard-hat::before { content: "\f82c"; } .fa-user-headset::before { content: "\f82d"; } .fa-user-helmet-safety::before { content: "\f82c"; } +.fa-user-hoodie::before { content: "\e68a"; } .fa-user-injured::before { content: "\f728"; } .fa-user-large::before { content: "\f406"; } .fa-user-large-slash::before { content: "\f4fa"; } @@ -4833,7 +4686,7 @@ readers do not read off random characters that represent icons */ border-width: 0; } /*! - * Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com + * Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license (Commercial License) * Copyright 2024 Fonticons, Inc. */ @@ -4954,6 +4807,9 @@ readers do not read off random characters that represent icons */ .fa-jxl:before { content: "\e67b"; } +.fa-dart-lang:before { + content: "\e693"; } + .fa-hire-a-helper:before { content: "\f3b0"; } @@ -5893,6 +5749,9 @@ readers do not read off random characters that represent icons */ .fa-twitch:before { content: "\f1e8"; } +.fa-flutter:before { + content: "\e694"; } + .fa-ravelry:before { content: "\f2d9"; } @@ -6428,7 +6287,7 @@ readers do not read off random characters that represent icons */ content: "\f3f6"; } /*! - * Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com + * Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license (Commercial License) * Copyright 2024 Fonticons, Inc. */ @@ -6448,7 +6307,7 @@ readers do not read off random characters that represent icons */ font-weight: 300; } /*! - * Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com + * Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license (Commercial License) * Copyright 2024 Fonticons, Inc. */ @@ -6468,7 +6327,7 @@ readers do not read off random characters that represent icons */ font-weight: 400; } /*! - * Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com + * Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license (Commercial License) * Copyright 2024 Fonticons, Inc. */ @@ -6488,7 +6347,7 @@ readers do not read off random characters that represent icons */ font-weight: 900; } /*! - * Font Awesome Pro 6.5.2 by @fontawesome - https://fontawesome.com + * Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license (Commercial License) * Copyright 2024 Fonticons, Inc. */ @@ -7505,6 +7364,9 @@ readers do not read off random characters that represent icons */ .fad.fa-trillium::after, .fa-duotone.fa-trillium::after { content: "\e588\e588"; } +.fad.fa-table-cells-unlock::after, .fa-duotone.fa-table-cells-unlock::after { + content: "\e692\e692"; } + .fad.fa-music-slash::after, .fa-duotone.fa-music-slash::after { content: "\f8d1\f8d1"; } @@ -8084,6 +7946,9 @@ readers do not read off random characters that represent icons */ .fad.fa-phone-square-alt::after, .fa-duotone.fa-phone-square-alt::after { content: "\f87b\f87b"; } +.fad.fa-user-beard-bolt::after, .fa-duotone.fa-user-beard-bolt::after { + content: "\e689\e689"; } + .fad.fa-cart-plus::after, .fa-duotone.fa-cart-plus::after { content: "\f217\f217"; } @@ -9581,6 +9446,9 @@ readers do not read off random characters that represent icons */ .fad.fa-coffin-cross::after, .fa-duotone.fa-coffin-cross::after { content: "\e051\e051"; } +.fad.fa-octopus::after, .fa-duotone.fa-octopus::after { + content: "\e688\e688"; } + .fad.fa-spell-check::after, .fa-duotone.fa-spell-check::after { content: "\f891\f891"; } @@ -9770,6 +9638,12 @@ readers do not read off random characters that represent icons */ .fad.fa-passport::after, .fa-duotone.fa-passport::after { content: "\f5ab\f5ab"; } +.fad.fa-thumbtack-slash::after, .fa-duotone.fa-thumbtack-slash::after { + content: "\e68f\e68f"; } + +.fad.fa-thumb-tack-slash::after, .fa-duotone.fa-thumb-tack-slash::after { + content: "\e68f\e68f"; } + .fad.fa-inbox-in::after, .fa-duotone.fa-inbox-in::after { content: "\f310\f310"; } @@ -11090,6 +10964,9 @@ readers do not read off random characters that represent icons */ .fad.fa-ufo-beam::after, .fa-duotone.fa-ufo-beam::after { content: "\e048\e048"; } +.fad.fa-hydra::after, .fa-duotone.fa-hydra::after { + content: "\e686\e686"; } + .fad.fa-circle-caret-up::after, .fa-duotone.fa-circle-caret-up::after { content: "\f331\f331"; } @@ -11783,6 +11660,9 @@ readers do not read off random characters that represent icons */ .fad.fa-podium::after, .fa-duotone.fa-podium::after { content: "\f680\f680"; } +.fad.fa-diamonds-4::after, .fa-duotone.fa-diamonds-4::after { + content: "\e68b\e68b"; } + .fad.fa-memo-circle-check::after, .fa-duotone.fa-memo-circle-check::after { content: "\e1d9\e1d9"; } @@ -15038,6 +14918,9 @@ readers do not read off random characters that represent icons */ .fad.fa-pipe-valve::after, .fa-duotone.fa-pipe-valve::after { content: "\e439\e439"; } +.fad.fa-lightbulb-message::after, .fa-duotone.fa-lightbulb-message::after { + content: "\e687\e687"; } + .fad.fa-arrow-up-from-arc::after, .fa-duotone.fa-arrow-up-from-arc::after { content: "\e4b4\e4b4"; } @@ -15554,6 +15437,9 @@ readers do not read off random characters that represent icons */ .fad.fa-oil-well::after, .fa-duotone.fa-oil-well::after { content: "\e532\e532"; } +.fad.fa-table-cells-column-unlock::after, .fa-duotone.fa-table-cells-column-unlock::after { + content: "\e690\e690"; } + .fad.fa-person-simple::after, .fa-duotone.fa-person-simple::after { content: "\e220\e220"; } @@ -15851,6 +15737,9 @@ readers do not read off random characters that represent icons */ .fad.fa-toolbox::after, .fa-duotone.fa-toolbox::after { content: "\f552\f552"; } +.fad.fa-globe-wifi::after, .fa-duotone.fa-globe-wifi::after { + content: "\e685\e685"; } + .fad.fa-envelope-dot::after, .fa-duotone.fa-envelope-dot::after { content: "\e16f\e16f"; } @@ -16586,6 +16475,9 @@ readers do not read off random characters that represent icons */ .fad.fa-transporter-2::after, .fa-duotone.fa-transporter-2::after { content: "\e044\e044"; } +.fad.fa-user-hoodie::after, .fa-duotone.fa-user-hoodie::after { + content: "\e68a\e68a"; } + .fad.fa-hands-holding-diamond::after, .fa-duotone.fa-hands-holding-diamond::after { content: "\f47c\f47c"; } @@ -19121,6 +19013,9 @@ readers do not read off random characters that represent icons */ .fad.fa-brain-circuit::after, .fa-duotone.fa-brain-circuit::after { content: "\e0c6\e0c6"; } +.fad.fa-table-cells-row-unlock::after, .fa-duotone.fa-table-cells-row-unlock::after { + content: "\e691\e691"; } + .fad.fa-user-injured::after, .fa-duotone.fa-user-injured::after { content: "\f728\f728"; } @@ -19394,6 +19289,7 @@ readers do not read off random characters that represent icons */ .fak.fa-fa-dock-l::before, .fa-kit.fa-fa-dock-l::before { content: "\e007"; } .fak.fa-fa-dock-r::before, .fa-kit.fa-fa-dock-r::before { content: "\e006"; } .fak.fa-lexon::before, .fa-kit.fa-lexon::before { content: "\e004"; } +.fak.fa-solid-gear-circle-play::before, .fa-kit.fa-solid-gear-circle-play::before { content: "\e009"; } .fak.fa-solidity-mono::before, .fa-kit.fa-solidity-mono::before { content: "\e005"; } .fak.fa-ts-logo::before, .fa-kit.fa-ts-logo::before { content: "\e003"; } .fak.fa-vyper2::before, .fa-kit.fa-vyper2::before { content: "\e002"; } diff --git a/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.ttf b/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.ttf index cd8888a51e..e4c5fc4700 100644 Binary files a/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.ttf and b/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.ttf differ diff --git a/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.woff2 b/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.woff2 index d107b4d1c3..8ca1dcdad3 100644 Binary files a/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.woff2 and b/apps/remix-ide/src/assets/fontawesome/webfonts/custom-icons.woff2 differ diff --git a/apps/remix-ide/src/assets/img/solid-gear-circle-play.svg b/apps/remix-ide/src/assets/img/solid-gear-circle-play.svg new file mode 100644 index 0000000000..6345426e91 --- /dev/null +++ b/apps/remix-ide/src/assets/img/solid-gear-circle-play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 3a11908675..58702393ca 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -25,6 +25,7 @@ let requiredModules = [ 'blockchain', 'web3Provider', 'scriptRunner', + 'scriptRunnerBridge', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', @@ -109,6 +110,10 @@ const isVM = (name) => { return name.startsWith('vm') } +const isScriptRunner = (name) => { + return name.startsWith('scriptRunner') +} + export function isNative(name) { // nativePlugin allows to bypass the permission request @@ -142,7 +147,7 @@ export function isNative(name) { 'walletconnect', 'contract-verification' ] - return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) + return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) || isScriptRunner(name) } /** @@ -195,6 +200,8 @@ export class RemixAppManager extends PluginManager { } } await this.toggleActive(name) + }else{ + console.log('cannot deactivate', name) } } @@ -302,6 +309,7 @@ export class RemixAppManager extends PluginManager { return plugins.map(plugin => { if (plugin.name === 'dgit' && Registry.getInstance().get('platform').api.isDesktop()) { plugin.url = 'https://dgit4-76cc9.web.app/' } if (plugin.name === testPluginName) plugin.url = testPluginUrl + //console.log('plugin', plugin) return new IframePlugin(plugin) }) } diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index c6cbfa21c0..f81bcb5037 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -30,7 +30,6 @@ export class RemixEngine extends Engine { if (name === 'remixAI') return { queueTimeout: 60000 * 20 } if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 } if (name === 'contentImport') return { queueTimeout: 60000 * 3 } - return { queueTimeout: 10000 } } diff --git a/libs/remix-api/src/lib/plugins/fileSystem-api.ts b/libs/remix-api/src/lib/plugins/fileSystem-api.ts index 84210f2f7d..7d87220db1 100644 --- a/libs/remix-api/src/lib/plugins/fileSystem-api.ts +++ b/libs/remix-api/src/lib/plugins/fileSystem-api.ts @@ -9,5 +9,6 @@ export interface IExtendedFileSystem extends IFileSystem { refresh(): Promise hasGitSubmodules(): Promise isGitRepo(): Promise + exists(file: string): Promise }; } \ No newline at end of file diff --git a/libs/remix-api/src/lib/plugins/menuicons-api.ts b/libs/remix-api/src/lib/plugins/menuicons-api.ts new file mode 100644 index 0000000000..41f5715c02 --- /dev/null +++ b/libs/remix-api/src/lib/plugins/menuicons-api.ts @@ -0,0 +1,14 @@ +import { IFilePanel } from '@remixproject/plugin-api' +import { Profile, StatusEvents } from '@remixproject/plugin-utils' + +export interface IMenuIconsApi { + events: { + toggleContent: (name: string) => void, + showContent: (name: string) => void + } & StatusEvents + methods: { + select: (name: string) => void + linkContent: (profile: Profile) => void + unlinkContent: (profile: Profile) => void + } +} diff --git a/libs/remix-api/src/lib/remix-api.ts b/libs/remix-api/src/lib/remix-api.ts index 75e9e6476d..e8dad3b599 100644 --- a/libs/remix-api/src/lib/remix-api.ts +++ b/libs/remix-api/src/lib/remix-api.ts @@ -15,6 +15,7 @@ import { ILayoutApi } from "./plugins/layout-api" import { IMatomoApi } from "./plugins/matomo-api" import { IRemixAI } from "./plugins/remixai-api" import { IRemixAID } from "./plugins/remixAIDesktop-api" +import { IMenuIconsApi } from "./plugins/menuicons-api" import { IDgitPlugin } from "./plugins/dgitplugin-api" export interface ICustomRemixApi extends IRemixApi { @@ -33,6 +34,7 @@ export interface ICustomRemixApi extends IRemixApi { pinnedPanel: IPinnedPanelApi layout: ILayoutApi matomo: IMatomoApi + menuicons: IMenuIconsApi remixAI: IRemixAI, remixAID: IRemixAID } diff --git a/libs/remix-simulator/src/methods/accounts.ts b/libs/remix-simulator/src/methods/accounts.ts index 6c99565455..c28818c8e6 100644 --- a/libs/remix-simulator/src/methods/accounts.ts +++ b/libs/remix-simulator/src/methods/accounts.ts @@ -76,6 +76,7 @@ export class Web3Accounts { methods (): Record { return { + eth_requestAccounts: this.eth_requestAccounts.bind(this), eth_accounts: this.eth_accounts.bind(this), eth_getBalance: this.eth_getBalance.bind(this), eth_sign: this.eth_sign.bind(this), @@ -85,6 +86,10 @@ export class Web3Accounts { } } + eth_requestAccounts (_payload, cb) { + return cb(null, Object.keys(this.accounts)) + } + eth_accounts (_payload, cb) { return cb(null, Object.keys(this.accounts)) } diff --git a/libs/remix-simulator/src/methods/transactions.ts b/libs/remix-simulator/src/methods/transactions.ts index 714852f693..d2bf5f6b2b 100644 --- a/libs/remix-simulator/src/methods/transactions.ts +++ b/libs/remix-simulator/src/methods/transactions.ts @@ -308,7 +308,6 @@ export class Transactions { const txBlock = this.vmContext.blockByTxHash[receipt.transactionHash] const tx = this.vmContext.txByHash[receipt.transactionHash] - // TODO: params to add later const r: Record = { blockHash: bytesToHex(txBlock.hash()), @@ -322,11 +321,10 @@ export class Transactions { input: receipt.input, nonce: bigIntToHex(tx.nonce), transactionIndex: this.TX_INDEX, - value: bigIntToHex(tx.value) - // "value":"0xf3dbb76162000" // 4290000000000000 - // "v": "0x25", // 37 - // "r": "0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea", - // "s": "0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c" + value: bigIntToHex(tx.value), + v: bigIntToHex(tx.v), + r: bigIntToHex(tx.r), + s: bigIntToHex(tx.s) } if (receipt.to) { diff --git a/libs/remix-ui/scriptrunner/src/index.ts b/libs/remix-ui/scriptrunner/src/index.ts new file mode 100644 index 0000000000..a1683b6b22 --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/index.ts @@ -0,0 +1,2 @@ +export { ScriptRunnerUI } from './lib/script-runner-ui'; +export * from './types'; \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx new file mode 100644 index 0000000000..be0959bd5b --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx @@ -0,0 +1,168 @@ +import React, { useEffect, useState } from "react"; +import { customScriptRunnerConfig, Dependency, ProjectConfiguration } from "../types"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faToggleOff, faToggleOn, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { CustomTooltip } from "@remix-ui/helper"; + +export interface ScriptRunnerUIProps { + publishedConfigurations: ProjectConfiguration[]; + openCustomConfig: () => any; + saveCustomConfig(content: customScriptRunnerConfig): void; + activateCustomScriptRunner(config: customScriptRunnerConfig): Promise; + customConfig: customScriptRunnerConfig; +} + +export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { + const [dependencies, setDependencies] = useState([]); + const [name, setName] = useState(''); + const [alias, setAlias] = useState(''); + const [version, setVersion] = useState(''); + const [baseConfig, setBaseConfig] = useState('default'); + const [loading, setLoading] = useState(false); + const [useRequire, setUseRequire] = useState(false) + + const { customConfig } = props; + + useEffect(() => { + if (!customConfig) return; + setDependencies(customConfig.dependencies); + setBaseConfig(customConfig.baseConfiguration); + },[customConfig]) + + const handleAddDependency = () => { + if (name.trim() && version.trim()) { + const newDependency: Dependency = { name, version, require: useRequire, alias }; + setDependencies([...dependencies, newDependency]); + setName(''); + setVersion(''); + } else { + alert('Please fill out both name and version.'); + } + }; + + const handleRemoveDependency = (index: number) => { + const updatedDependencies = dependencies.filter((_, i) => i !== index); + setDependencies(updatedDependencies); + }; + + const handleSaveToFile = () => { + const fileData = JSON.stringify(dependencies, null, 2); + console.log(fileData, baseConfig); + const customConfig: customScriptRunnerConfig = { baseConfiguration: baseConfig, dependencies }; + console.log(customConfig); + props.saveCustomConfig(customConfig); + }; + + const openConfig = async () => { + const fileData: customScriptRunnerConfig = await props.openCustomConfig(); + } + + const activateCustomConfig = async () => { + const customConfig: customScriptRunnerConfig = { baseConfiguration: baseConfig, dependencies }; + setLoading(true); + try { + await props.activateCustomScriptRunner(customConfig); + } catch (e) { + console.log(e) + } finally { + setLoading(false); + } + } + + const onSelectBaseConfig = (e: React.ChangeEvent) => { + setBaseConfig(e.target.value); + } + + const toggleRequire = () => { + setUseRequire((prev) => !prev) + } + + if (loading) { + return
+
+ +
+
+ } + + return ( +
+
Custom configuration
+ + + +
+ setName(e.target.value)} + style={{ marginRight: '10px' }} + /> + setAlias(e.target.value)} /> + setVersion(e.target.value)} + /> + +
+ + +
+
+ +
+
    + {dependencies.map((dependency, index) => ( +
  • +
    + {dependency.name} - {dependency.version} + +
    +
  • + ))} +
+ {dependencies.length > 0 && ( + + )} + + {dependencies.length > 0 && ( + )} +
+ ); +} \ No newline at end of file diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx new file mode 100644 index 0000000000..52692be16f --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -0,0 +1,103 @@ +import React, { useEffect, useState } from "react"; +import { Accordion, Button } from "react-bootstrap"; +import { customScriptRunnerConfig, ProjectConfiguration } from "../types"; +import { faCaretDown, faCaretRight, faCheck, faExclamationCircle, faRedoAlt, faToggleOn } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { CustomScriptRunner } from "./custom-script-runner"; +import { CustomTooltip } from "@remix-ui/helper"; + +export interface ScriptRunnerUIProps { + loadScriptRunner: (config: ProjectConfiguration) => void; + openCustomConfig: () => any; + saveCustomConfig(content: customScriptRunnerConfig): void; + activateCustomScriptRunner(config: customScriptRunnerConfig): Promise; + customConfig: customScriptRunnerConfig; + configurations: ProjectConfiguration[]; + activeConfig: ProjectConfiguration; + enableCustomScriptRunner: boolean; +} + +export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { + const { loadScriptRunner, configurations, activeConfig, enableCustomScriptRunner } = props; + const [activeKey, setActiveKey] = useState('default'); + + useEffect(() => { + if (activeConfig) { + setActiveKey(activeConfig.name) + } + },[activeConfig]) + + if (!configurations) { + return
Loading...
; + } + + return ( +
+ + {configurations.filter((config) => config.publish).map((config: ProjectConfiguration, index) => ( +
+
+ setActiveKey(activeKey === config.name ? '' : config.name)} + > +
+ {activeKey === config.name ? + : + } +
{config.title || config.name}
+
+
+
+ {config.isLoading &&
+ +
} + {config.errorStatus && config.error &&
+ + + + +
} + {!config.isLoading && config.errorStatus && config.error && +
loadScriptRunner(config)} className="pointer px-2"> + +
} + {!config.isLoading && !config.errorStatus && !config.error && +
loadScriptRunner(config)} className="pointer px-2"> + {activeConfig && activeConfig.name !== config.name ? + : + + } +
+ } +
+
+ + + <> +

Description: {config.description}

+

Dependencies:

+
    + {config.dependencies.map((dep, depIndex) => ( +
  • + {dep.name} (v{dep.version}) +
  • + ))} +
+
))} +
+ {enableCustomScriptRunner && + config.publish)} + />} +
+ ); +}; + diff --git a/libs/remix-ui/scriptrunner/src/types/index.ts b/libs/remix-ui/scriptrunner/src/types/index.ts new file mode 100644 index 0000000000..d675fa2084 --- /dev/null +++ b/libs/remix-ui/scriptrunner/src/types/index.ts @@ -0,0 +1,37 @@ +import { defaultConfig } from "@web3modal/ethers5/react"; + +export interface Dependency { + version: string; + name: string; + alias?: string; + import?: boolean; + require: boolean; + windowImport?: boolean; + } + +export interface Replacements { + [key: string]: string; + } + +export interface ProjectConfiguration { + name: string; + publish: boolean; + description: string; + dependencies: Dependency[]; + replacements: Replacements; + title: string; + errorStatus: boolean; + error: string; + isLoading: boolean; + } + +export interface customScriptRunnerConfig { + baseConfiguration: string; + dependencies: Dependency[]; + } + +export interface ScriptRunnerConfig { + defaultConfig: string, + customConfig: customScriptRunnerConfig + } + diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index bfa136f185..eaaffcad15 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -1,7 +1,7 @@ import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' import { CustomTooltip } from '@remix-ui/helper' import { Plugin } from '@remixproject/engine' -import React, {useState, useRef, useEffect, useReducer} from 'react' // eslint-disable-line +import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import { FormattedMessage } from 'react-intl' import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import './remix-ui-tabs.css' @@ -86,10 +86,10 @@ export const TabsUI = (props: TabsUIProps) => { } }, [tabsState.selectedIndex]) - const getAI = async() => { + const getAI = async () => { try { return await props.plugin.call('settings', 'getCopilotSetting') - } catch (e){ + } catch (e) { return false } } @@ -208,7 +208,7 @@ export const TabsUI = (props: TabsUIProps) => { const path = active().substr(active().indexOf('/') + 1, active().length) const content = await props.plugin.call('fileManager', 'readFile', path) if (tabsState.currentExt === 'js' || tabsState.currentExt === 'ts') { - await props.plugin.call('scriptRunner', 'execute', content, path) + await props.plugin.call('scriptRunnerBridge', 'execute', content, path) _paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt]) } else if (tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul') { await props.plugin.call('solidity', 'compile', path) @@ -225,14 +225,32 @@ export const TabsUI = (props: TabsUIProps) => { + {(tabsState.currentExt === 'ts' || tabsState.currentExt === 'js') -
+ && + + + }> + } +
- {tabsState.currentExt === 'sol'? ( + {tabsState.currentExt === 'sol' ? ( ) : ( @@ -289,7 +307,7 @@ export const TabsUI = (props: TabsUIProps) => { tooltipId="overlay-tooltip-copilot" tooltipText={ - { tabsState.currentExt === 'sol'? ( + {tabsState.currentExt === 'sol' ? ( !ai_switch ? ( ) : () @@ -303,7 +321,7 @@ export const TabsUI = (props: TabsUIProps) => { data-id="remix_ai_switch" id='remix_ai_switch' className="btn ai-switch text-ai pl-2 pr-0 py-0" - disabled={!(tabsState.currentExt === 'sol' )} + disabled={!(tabsState.currentExt === 'sol')} onClick={async () => { await props.plugin.call('settings', 'updateCopilotChoice', !ai_switch) setAI_switch(!ai_switch) @@ -315,7 +333,7 @@ export const TabsUI = (props: TabsUIProps) => {
-
+
}> props.onZoomOut()}> diff --git a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts index ceeea496f7..9d9abbb760 100644 --- a/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts +++ b/libs/remix-ui/terminal/src/lib/actions/terminalAction.ts @@ -79,28 +79,28 @@ export const filterFnAction = (name: string, filterFn, dispatch: React.Dispatch< } export const registerLogScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + on('scriptRunnerBridge', commandName, (msg) => { commandFn.log.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) } export const registerInfoScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + on('scriptRunnerBridge', commandName, (msg) => { commandFn.info.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) } export const registerWarnScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + on('scriptRunnerBridge', commandName, (msg) => { commandFn.warn.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) } export const registerErrorScriptRunnerAction = (on, commandName, commandFn, dispatch: React.Dispatch) => { - on('scriptRunner', commandName, (msg) => { + on('scriptRunnerBridge', commandName, (msg) => { commandFn.error.apply(commandFn, msg.data) // eslint-disable-line dispatch({ type: commandName, payload: { commandFn, message: msg.data } }) }) diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index e7a4feb0aa..4e0d224c1b 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -245,7 +245,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { await call('remixAI', 'solidity_answer', script) // No streaming supported in terminal _paq.push(['trackEvent', 'ai', 'remixAI', 'askFromTerminal']) } else { - await call('scriptRunner', 'execute', script) + await call('scriptRunnerBridge', 'execute', script) } done() } catch (error) { diff --git a/libs/remix-ui/workspace/src/lib/actions/index.tsx b/libs/remix-ui/workspace/src/lib/actions/index.tsx index 7478ef3aaa..7385b1316e 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.tsx +++ b/libs/remix-ui/workspace/src/lib/actions/index.tsx @@ -505,7 +505,7 @@ export const runScript = async (path: string) => { if (error) { return dispatch(displayPopUp(error)) } - plugin.call('scriptRunner', 'execute', content, path) + plugin.call('scriptRunnerBridge', 'execute', content, path) }) } diff --git a/tsconfig.paths.json b/tsconfig.paths.json index d373a783b1..a61989661d 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -188,6 +188,9 @@ "@remix-api": [ "libs/remix-api/src/index.ts" ], + "@remix-scriptrunner": [ + "libs/remix-ui/scriptrunner/src/index.ts" + ], "@remix-git": [ "libs/remix-git/" ]