diff --git a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx index 793db0cf42..b20cf820a3 100644 --- a/apps/remix-ide/src/app/tabs/script-runner-ui.tsx +++ b/apps/remix-ide/src/app/tabs/script-runner-ui.tsx @@ -1,10 +1,14 @@ import { IframePlugin, IframeProfile, ViewPlugin } from '@remixproject/engine-web' import * as packageJson from '../../../../../package.json' import React from 'react' // eslint-disable-line -import { customScriptRunnerConfig, Dependency, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line +import { customScriptRunnerConfig, Dependency, ProjectConfiguration, ScriptRunnerUI } from '@remix-scriptrunner' // eslint-disable-line import { Profile } from '@remixproject/plugin-utils' -import { Engine } from '@remixproject/engine' +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', @@ -19,30 +23,99 @@ const profile = { maintainedBy: 'Remix' } +const configFileName = 'script.config.json' + export class ScriptRunnerUIPlugin extends ViewPlugin { engine: Engine current: string currentTemplate: string + dispatch: React.Dispatch = () => { } + workspaceScriptRunnerDefaults: Record + customConfig: customScriptRunnerConfig + configurations: ProjectConfiguration[] + plugin: Plugin constructor(engine: Engine) { super(profile) console.log('ScriptRunnerUIPlugin', this) this.engine = engine + this.workspaceScriptRunnerDefaults = {} + this.plugin = this } async onActivation() { console.log('onActivation', this) + + console.log('onActivation', this.customConfig) + + this.on('filePanel', 'setWorkspace', async (workspace: string) => { + console.log('setWorkspace', workspace, this) + this.customConfig = { + baseConfiguration: 'default', + dependencies: [] + } + await this.loadCustomConfig() + this.loadConfigurations() + this.renderComponent() + console.log('setWorkspace', this.customConfig) + }) + + this.plugin.on('fileManager','fileSaved', async (file: string) =>{ + console.log(file) + if(file === configFileName) { + await this.loadCustomConfig() + this.renderComponent() + } + }) + + await this.loadCustomConfig() + this.loadConfigurations() + this.renderComponent() + } + + render() { + return ( +
+ +
+ ) + } + + setDispatch(dispatch: React.Dispatch) { + this.dispatch = dispatch + this.renderComponent() + } + + renderComponent() { + this.dispatch({ + customConfig: this.customConfig, + configurations: this.configurations, + }) + } + + updateComponent(state: any) { + console.log('updateComponent', state) + return ( + + ) } async loadScriptRunner(name: string) { console.log('loadScriptRunner', name) - const profile: IframeProfile = await this.call('manager', 'getProfile', 'scriptRunner') + const profile: Profile = await this.plugin.call('manager', 'getProfile', 'scriptRunner') const testPluginName = localStorage.getItem('test-plugin-name') const testPluginUrl = localStorage.getItem('test-plugin-url') let baseUrl = 'http://localhost:3000' - let url = `${baseUrl}?template=${name}` - if(testPluginName === 'scriptRunner') { + let url = `${baseUrl}?template=${name}×tamp=${Date.now()}` + if (testPluginName === 'scriptRunner') { // if testpluginurl has template specified only use that - if (testPluginUrl.indexOf('template')>-1) { + if (testPluginUrl.indexOf('template') > -1) { url = testPluginUrl } else { baseUrl = `//${new URL(testPluginUrl).host}` @@ -53,19 +126,21 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { const newProfile: IframeProfile = { ...profile, name: profile.name + name, + location: 'hidden', url: url } console.log('loadScriptRunner', newProfile) try { const plugin: IframePlugin = new IframePlugin(newProfile) await this.engine.register(plugin) - await this.call('manager', 'activatePlugin', newProfile.name) + await this.plugin.call('manager', 'activatePlugin', newProfile.name) this.current = newProfile.name this.currentTemplate = name 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)) } catch (e) { this.current = newProfile.name this.currentTemplate = name @@ -79,6 +154,30 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { await this.call(this.current, 'execute', script, filePath) } + async dependencyError(data: any) { + console.log('dependencyError', 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) { console.log('log', data) this.emit('log', data) @@ -103,39 +202,95 @@ export class ScriptRunnerUIPlugin extends ViewPlugin { console.log('buildScriptRunner', dependencies) } - async loadCustomConfig(){ + async loadCustomConfig(): Promise { console.log('loadCustomConfig') - await this.call('fileManager', 'open', 'script.config.json') - const content = await this.call('fileManager', 'readFile', 'script.config.json') - return JSON.parse(content) + //await this.plugin.call('fileManager', 'open', 'script.config.json') + try { + const content = await this.plugin.call('fileManager', 'readFile', configFileName) + const parsed = JSON.parse(content) + this.customConfig = parsed + } catch (e) { + return { + baseConfiguration: 'default', + dependencies: [] + } + } + } + + async openCustomConfig() { + try { + await this.plugin.call('fileManager', 'open', 'script.config.json') + }catch(e){ + + } } - async saveCustomConfig(content: customScriptRunnerConfig){ + async loadConfigurations() { + try { + const response = await axios.get('http://localhost:3000/projects.json?timestamp=' + Date.now()); + this.configurations = response.data; + } catch (error) { + console.error("Error fetching the projects data:", error); + } + + } + + async saveCustomConfig(content: customScriptRunnerConfig) { console.log('saveCustomConfig', content) - await this.call('fileManager', 'writeFile', 'script.config.json', JSON.stringify(content, null, 2)) + await this.plugin.call('fileManager', 'writeFile', 'script.config.json', JSON.stringify(content, null, 2)) } - async activateCustomScriptRunner(config: customScriptRunnerConfig){ + async activateCustomScriptRunner(config: customScriptRunnerConfig) { console.log('activateCustomScriptRunner', config) // post config to localhost:4000 using axios - const result = await axios.post('http://localhost:4000/build', config) - console.log(result) - if(result.data.hash) { - await this.loadScriptRunner(result.data.hash) + try { + const result = await axios.post('http://localhost:4000/build', config) + console.log(result) + if (result.data.hash) { + 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) + } + } - return result.data.hash } - render() { - return ( -
- -
- ) - } + } \ 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 56628dc5b0..3620a1ac18 100644 --- a/libs/remix-api/src/lib/remix-api.ts +++ b/libs/remix-api/src/lib/remix-api.ts @@ -12,6 +12,7 @@ import { ISidePanelApi } from "./plugins/sidePanel-api" import { IPinnedPanelApi } from "./plugins/pinned-panel-api" import { ILayoutApi } from "./plugins/layout-api" import { IMatomoApi } from "./plugins/matomo-api" +import { IMenuIconsApi } from "./plugins/menuicons-api" export interface ICustomRemixApi extends IRemixApi { dgitApi: IGitApi @@ -25,6 +26,7 @@ export interface ICustomRemixApi extends IRemixApi { pinnedPanel: IPinnedPanelApi layout: ILayoutApi matomo: IMatomoApi + menuicons: IMenuIconsApi } export declare type CustomRemixApi = Readonly \ 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 index 78ccb7cedd..51e611edc3 100644 --- a/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx @@ -3,16 +3,19 @@ import { Accordion, Card, Button } from "react-bootstrap"; import axios from "axios"; import { customScriptRunnerConfig, Dependency, ProjectConfiguration } from "../types"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faToggleOff, faToggleOn, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { CustomTooltip } from "@remix-ui/helper"; +import { use } from "chai"; export interface ScriptRunnerUIProps { // build custom script runner buildScriptRunner: (dependencies: Dependency[]) => void; publishedConfigurations: ProjectConfiguration[]; - loadCustomConfig: () => any; + openCustomConfig: () => any; saveCustomConfig(content: customScriptRunnerConfig): void; activateCustomScriptRunner(config: customScriptRunnerConfig): string; - addCustomConfig(config: ProjectConfiguration) : void; + addCustomConfig(config: ProjectConfiguration): void; + customConfig: customScriptRunnerConfig; } export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { @@ -22,10 +25,24 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { const [version, setVersion] = useState(''); const [baseConfig, setBaseConfig] = useState('default'); const [loading, setLoading] = useState(false); + const [useRequire, setUseRequire] = useState(false) + + const { customConfig } = props; + + useEffect(() =>{ + console.log('CustomScriptRunner', props.customConfig) + },[]) + + useEffect(() => { + console.log('CustomScriptRunner', props.customConfig) + if(!customConfig) return; + setDependencies(customConfig.dependencies); + setBaseConfig(customConfig.baseConfiguration); + },[customConfig]) const handleAddDependency = () => { if (name.trim() && version.trim()) { - const newDependency: Dependency = { name, version, import: true, alias }; + const newDependency: Dependency = { name, version, require: useRequire, alias }; setDependencies([...dependencies, newDependency]); setName(''); setVersion(''); @@ -39,6 +56,14 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { setDependencies(updatedDependencies); }; + useEffect(() => { + async function saveData() { + //await handleSaveToFile(); + } + + saveData(); + },[dependencies]) + const handleSaveToFile = () => { const fileData = JSON.stringify(dependencies, null, 2); console.log(fileData, baseConfig); @@ -47,11 +72,8 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { props.saveCustomConfig(customConfig); }; - const loadFromFile = async () => { - const fileData: customScriptRunnerConfig = await props.loadCustomConfig(); - console.log(fileData); - setDependencies(fileData.dependencies); - setBaseConfig(fileData.baseConfiguration); + const openConfig = async () => { + const fileData: customScriptRunnerConfig = await props.openCustomConfig(); } const activateCustomConfig = async () => { @@ -59,24 +81,34 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { const customConfig: customScriptRunnerConfig = { baseConfiguration: baseConfig, dependencies }; console.log(customConfig); setLoading(true); - const loadedConfig = await props.activateCustomScriptRunner(customConfig); - console.log(loadedConfig); - const newConfig: ProjectConfiguration = { - name: loadedConfig, - publish: true, - description: `Extension of ${baseConfig}`, - dependencies: dependencies, - replacements: {} - }; - console.log(newConfig); - props.addCustomConfig(newConfig); - setLoading(false); + try { + const loadedConfig = await props.activateCustomScriptRunner(customConfig); + console.log(loadedConfig); + const newConfig: ProjectConfiguration = { + name: loadedConfig, + publish: true, + description: `Extension of ${baseConfig}`, + dependencies: dependencies, + replacements: {} + }; + console.log(newConfig); + props.addCustomConfig(newConfig); + + } 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
@@ -120,6 +152,15 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => { value={version} onChange={(e) => setVersion(e.target.value)} /> + +
+ + +
+
+ {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 index bde50bac7b..7455aa60b1 100644 --- a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -15,29 +15,21 @@ export interface ScriptRunnerUIProps { loadScriptRunner: (name: string) => void; // build custom script runner buildScriptRunner: (dependencies: Dependency[]) => void; - loadCustomConfig: () => any; + openCustomConfig: () => any; saveCustomConfig(content: customScriptRunnerConfig): void; activateCustomScriptRunner(config: customScriptRunnerConfig): string; + customConfig: customScriptRunnerConfig; + configurations: ProjectConfiguration[]; } export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { - const { loadScriptRunner } = props; - const [configurations, setConfigurations] = useState([]); + const { loadScriptRunner, configurations } = props; const [activeKey, setActiveKey] = useState('default'); const [activeConfig, setActiveConfig] = useState('default'); useEffect(() => { // Fetch the JSON data from the localhost server using Axios - const fetchData = async () => { - try { - const response = await axios.get('http://localhost:3000/projects.json?timestamp=' + Date.now()); - setConfigurations(response.data); - } catch (error) { - console.error("Error fetching the projects data:", error); - } - }; - fetchData(); }, []); // Empty array ensures this effect runs once when the component mounts const handleSelect = (key) => { @@ -51,10 +43,14 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => { if(configurations.find((c) => c.name === config.name)) { return; } - setConfigurations([...configurations, config]); + //setConfigurations([...configurations, config]); setActiveConfig(config.name); } + if (!configurations) { + return
Loading...
; + } + return (
@@ -93,10 +89,11 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => {
))} config.publish)} buildScriptRunner={props.buildScriptRunner} /> diff --git a/libs/remix-ui/scriptrunner/src/types/index.ts b/libs/remix-ui/scriptrunner/src/types/index.ts index 85a88ce447..b474249ca6 100644 --- a/libs/remix-ui/scriptrunner/src/types/index.ts +++ b/libs/remix-ui/scriptrunner/src/types/index.ts @@ -2,8 +2,8 @@ export interface Dependency { version: string; name: string; alias?: string; - import: boolean; - require?: boolean; + import?: boolean; + require: boolean; windowImport?: boolean; }