sync configs

pull/5323/head
bunsenstraat 2 months ago
parent 9bf9b3ebe5
commit 54f82017c1
  1. 207
      apps/remix-ide/src/app/tabs/script-runner-ui.tsx
  2. 14
      libs/remix-api/src/lib/plugins/menuicons-api.ts
  3. 2
      libs/remix-api/src/lib/remix-api.ts
  4. 65
      libs/remix-ui/scriptrunner/src/lib/custom-script-runner.tsx
  5. 25
      libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx
  6. 4
      libs/remix-ui/scriptrunner/src/types/index.ts

@ -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<any> = () => { }
workspaceScriptRunnerDefaults: Record<string, string>
customConfig: customScriptRunnerConfig
configurations: ProjectConfiguration[]
plugin: Plugin<any, CustomRemixApi>
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 (
<div id="scriptrunnerTab">
<PluginViewWrapper plugin={this} />
</div>
)
}
setDispatch(dispatch: React.Dispatch<any>) {
this.dispatch = dispatch
this.renderComponent()
}
renderComponent() {
this.dispatch({
customConfig: this.customConfig,
configurations: this.configurations,
})
}
updateComponent(state: any) {
console.log('updateComponent', state)
return (
<ScriptRunnerUI
customConfig={state.customConfig}
configurations={state.configurations}
activateCustomScriptRunner={this.activateCustomScriptRunner.bind(this)}
saveCustomConfig={this.saveCustomConfig.bind(this)}
openCustomConfig={this.openCustomConfig.bind(this)}
buildScriptRunner={this.buildScriptRunner.bind(this)}
loadScriptRunner={this.loadScriptRunner.bind(this)} />
)
}
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}&timestamp=${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<customScriptRunnerConfig> {
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 saveCustomConfig(content: customScriptRunnerConfig){
async openCustomConfig() {
try {
await this.plugin.call('fileManager', 'open', 'script.config.json')
}catch(e){
}
}
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
try {
const result = await axios.post('http://localhost:4000/build', config)
console.log(result)
if(result.data.hash) {
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)
}
render() {
return (
<div id="scriptRunnerTab">
<ScriptRunnerUI
activateCustomScriptRunner={this.activateCustomScriptRunner.bind(this)}
saveCustomConfig={this.saveCustomConfig.bind(this)}
loadCustomConfig={this.loadCustomConfig.bind(this)}
buildScriptRunner={this.buildScriptRunner.bind(this)}
loadScriptRunner={this.loadScriptRunner.bind(this)} />
</div>
)
}
}
}

@ -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
}
}

@ -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<ICustomRemixApi>

@ -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<string>('');
const [baseConfig, setBaseConfig] = useState<string>('default');
const [loading, setLoading] = useState<boolean>(false);
const [useRequire, setUseRequire] = useState<boolean>(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,6 +81,7 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => {
const customConfig: customScriptRunnerConfig = { baseConfiguration: baseConfig, dependencies };
console.log(customConfig);
setLoading(true);
try {
const loadedConfig = await props.activateCustomScriptRunner(customConfig);
console.log(loadedConfig);
const newConfig: ProjectConfiguration = {
@ -70,13 +93,22 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => {
};
console.log(newConfig);
props.addCustomConfig(newConfig);
} catch (e) {
console.log(e)
} finally {
setLoading(false);
}
}
const onSelectBaseConfig = (e: React.ChangeEvent<HTMLSelectElement>) => {
setBaseConfig(e.target.value);
}
const toggleRequire = () => {
setUseRequire((prev) => !prev)
}
if (loading) {
return <div style={{ padding: '20px', maxWidth: '400px', margin: 'auto' }}>
<div className="text-center py-5">
@ -120,6 +152,15 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => {
value={version}
onChange={(e) => setVersion(e.target.value)}
/>
<CustomTooltip
placement="bottom"
tooltipText="use require when the module doesn't support import statements"
>
<div>
<label className="pr-2 pt-2">Use 'require':</label>
<FontAwesomeIcon className={useRequire ? 'text-success' : ''} onClick={toggleRequire} icon={useRequire ? faToggleOn : faToggleOff}></FontAwesomeIcon>
</div>
</CustomTooltip>
<button
className="btn btn-primary w-100 mt-1"
onClick={handleAddDependency}>
@ -144,11 +185,11 @@ export const CustomScriptRunner = (props: ScriptRunnerUIProps) => {
</ul>
{dependencies.length > 0 && (
<button className="btn btn-primary w-100" onClick={handleSaveToFile} style={{ marginTop: '20px' }}>
Save List to File
Save config
</button>
)}
<button className="btn btn-primary w-100" onClick={loadFromFile} style={{ marginTop: '20px' }}>
Load from File
<button className="btn btn-primary w-100" onClick={openConfig} style={{ marginTop: '20px' }}>
Open config
</button>
{dependencies.length > 0 && (
<button className="btn btn-success w-100" onClick={activateCustomConfig} style={{ marginTop: '20px' }}>

@ -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<ProjectConfiguration[]>([]);
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 <div>Loading...</div>;
}
return (
<div className="px-1">
@ -93,10 +89,11 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => {
</Accordion.Collapse></div>))}
</Accordion>
<CustomScriptRunner
customConfig={props.customConfig}
addCustomConfig={addCustomConfig}
activateCustomScriptRunner={props.activateCustomScriptRunner}
saveCustomConfig={props.saveCustomConfig}
loadCustomConfig={props.loadCustomConfig}
openCustomConfig={props.openCustomConfig}
publishedConfigurations={configurations.filter((config) => config.publish)}
buildScriptRunner={props.buildScriptRunner} />
</div>

@ -2,8 +2,8 @@ export interface Dependency {
version: string;
name: string;
alias?: string;
import: boolean;
require?: boolean;
import?: boolean;
require: boolean;
windowImport?: boolean;
}

Loading…
Cancel
Save