update test app

rdesktop^2
filip mertens 1 year ago
parent 272bf13344
commit 6534e8aa47
  1. 10
      apps/1test/src/app.tsx
  2. 24
      apps/1test/src/electron/engine.ts
  3. 90
      apps/1test/src/electron/fsPlugin.ts
  4. 30
      apps/1test/src/electron/gitPlugin.ts
  5. 43
      apps/1test/src/electron/lib/electronBasePlugin.ts
  6. 13
      apps/1test/src/electron/lib/electronPluginClient.ts
  7. 18
      apps/1test/src/electron/menus/commands.ts
  8. 36
      apps/1test/src/electron/menus/shell.ts
  9. 39
      apps/1test/src/electron/xtermPlugin.ts
  10. 1
      apps/1test/src/global.d.ts
  11. 49
      apps/1test/src/index.ts
  12. 22
      apps/1test/src/preload.ts
  13. 87
      apps/1test/src/remix/fsPlugin.ts
  14. 5
      apps/1test/src/remix/lib/electronPlugin.ts
  15. 40
      apps/1test/src/remix/ui/remix-ui-filedialog.tsx
  16. 3
      apps/1test/src/remix/ui/remix-ui-xterm.tsx
  17. 25
      apps/1test/src/remix/ui/remix-ui-xterminals.tsx
  18. 2
      apps/1test/src/remix/xtermPlugin.ts
  19. 20
      apps/1test/src/renderer.ts

@ -1,8 +1,14 @@
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import { RemixUiXterminals } from './remix/ui/remix-ui-xterminals'; import { RemixUiXterminals } from './remix/ui/remix-ui-xterminals';
import { xterm } from './renderer'; import { RemixUIFileDialog } from './remix/ui/remix-ui-filedialog';
import { xterm, filePanel } from './renderer';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
const container = document.getElementById('app'); const container = document.getElementById('app');
const root = createRoot(container); // createRoot(container!) if you use TypeScript const root = createRoot(container); // createRoot(container!) if you use TypeScript
root.render(<RemixUiXterminals plugin={xterm} />) root.render(
<>
<RemixUiXterminals plugin={xterm} />
<RemixUIFileDialog plugin={filePanel} />
</>
)

@ -1,29 +1,39 @@
import { Engine, PluginManager } from '@remixproject/engine'; import { Engine, PluginManager } from '@remixproject/engine';
import { ipcMain } from 'electron'; import { BrowserWindow, ipcMain } from 'electron';
import { FSPlugin } from './fsPlugin'; import { FSPlugin } from './fsPlugin';
import { GitPlugin } from './gitPlugin'; import { GitPlugin } from './gitPlugin';
import { app } from 'electron'; import { app } from 'electron';
import { XtermPlugin } from './xtermPlugin'; import { XtermPlugin } from './xtermPlugin';
const engine = new Engine() const engine = new Engine()
const appManager = new PluginManager() const appManager = new PluginManager()
const fsPlugin = new FSPlugin() const fsPlugin = new FSPlugin()
const gitPlugin = new GitPlugin() const gitPlugin = new GitPlugin()
const xtermPlugin = new XtermPlugin() const xtermPlugin = new XtermPlugin()
engine.register(appManager) engine.register(appManager)
engine.register(fsPlugin) engine.register(fsPlugin)
engine.register(gitPlugin) engine.register(gitPlugin)
engine.register(xtermPlugin) engine.register(xtermPlugin)
ipcMain.handle('manager:activatePlugin', async (event, arg) => { appManager.activatePlugin('fs')
console.log('manager:activatePlugin', arg) appManager.activatePlugin('git')
if(await appManager.isActive(arg)){ appManager.activatePlugin('xterm')
return true
} ipcMain.handle('manager:activatePlugin', async (event, plugin) => {
return await appManager.activatePlugin(arg) console.log('manager:activatePlugin', plugin, event.sender.id)
appManager.call(plugin, 'createClient', event.sender.id)
}) })
ipcMain.handle('getWebContentsID', (event, message) => {
return event.sender.id
})
app.on('before-quit', async () => { app.on('before-quit', async () => {
console.log('before-quit')
await appManager.call('fs', 'closeWatch') await appManager.call('fs', 'closeWatch')
app.quit() app.quit()
}) })

@ -1,41 +1,53 @@
import { PluginClient } from "@remixproject/plugin";
import { createElectronClient } from "./lib/electronPluginClient"
import { Plugin } from '@remixproject/engine';
import fs from 'fs/promises' import fs from 'fs/promises'
import { Stats } from "fs"; import { Stats } from "fs";
import { Profile } from "@remixproject/plugin-utils"; import { Profile } from "@remixproject/plugin-utils";
import chokidar from 'chokidar' import chokidar from 'chokidar'
import { ElectronBasePlugin, ElectronBasePluginClient, ElectronBasePluginInterface } from "./lib/electronBasePlugin";
import { dialog } from 'electron';
const profile: Profile = { const profile: Profile = {
displayName: 'fs', displayName: 'fs',
name: 'fs', name: 'fs',
description: 'fs', description: 'fs'
} }
export class FSPlugin extends Plugin { export class FSPlugin extends ElectronBasePlugin {
client: FSPluginClient clients: FSPluginClient[] = []
constructor() { constructor() {
super(profile) super(profile)
this.methods = ['closeWatch'] this.methods = [...super.methods, 'closeWatch']
}
async createClient(webContentsId: number): Promise<void> {
this.clients.push(new FSPluginClient(webContentsId))
} }
onActivation(): void { async closeClient(webContentsId: number): Promise<void> {
this.client = new FSPluginClient() console.log('closeClient', webContentsId)
} }
async closeWatch(): Promise<void> { async closeWatch(): Promise<void> {
console.log('closeWatch') for (const client of this.clients) {
await this.client.closeWatch() await client.closeWatch()
}
} }
} }
class FSPluginClient extends PluginClient { const clientProfile: Profile = {
name: 'fs',
displayName: 'fs',
description: 'fs',
methods: ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'currentPath', 'watch', 'closeWatch', 'setWorkingDir']
}
class FSPluginClient extends ElectronBasePluginClient {
watcher: chokidar.FSWatcher watcher: chokidar.FSWatcher
constructor() { workingDir: string = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/'
super()
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'watch', 'closeWatch', 'currentPath'] constructor(webContentsId: number) {
createElectronClient(this, profile) super(webContentsId, clientProfile)
this.onload(() => { this.onload(() => {
console.log('fsPluginClient onload') console.log('fsPluginClient onload')
}) })
@ -43,39 +55,39 @@ class FSPluginClient extends PluginClient {
async readdir(path: string): Promise<string[]> { async readdir(path: string): Promise<string[]> {
// call node fs.readdir // call node fs.readdir
return fs.readdir(path) return fs.readdir(this.fixPath(path))
} }
async readFile(path: string): Promise<string> { async readFile(path: string): Promise<string> {
return fs.readFile(path, 'utf8') return fs.readFile(this.fixPath(path), 'utf8')
} }
async writeFile(path: string, content: string): Promise<void> { async writeFile(path: string, content: string): Promise<void> {
return fs.writeFile(path, content, 'utf8') return fs.writeFile(this.fixPath(path), content, 'utf8')
} }
async mkdir(path: string): Promise<void> { async mkdir(path: string): Promise<void> {
return fs.mkdir(path) return fs.mkdir(this.fixPath(path))
} }
async rmdir(path: string): Promise<void> { async rmdir(path: string): Promise<void> {
return fs.rmdir(path) return fs.rmdir(this.fixPath(path))
} }
async unlink(path: string): Promise<void> { async unlink(path: string): Promise<void> {
return fs.unlink(path) return fs.unlink(this.fixPath(path))
} }
async rename(oldPath: string, newPath: string): Promise<void> { async rename(oldPath: string, newPath: string): Promise<void> {
return fs.rename(oldPath, newPath) return fs.rename(this.fixPath(oldPath), this.fixPath(newPath))
} }
async stat(path: string): Promise<Stats> { async stat(path: string): Promise<Stats> {
return fs.stat(path) return fs.stat(this.fixPath(path))
} }
async exists(path: string): Promise<boolean> { async exists(path: string): Promise<boolean> {
return fs.access(path).then(() => true).catch(() => false) return fs.access(this.fixPath(path)).then(() => true).catch(() => false)
} }
async currentPath(): Promise<string> { async currentPath(): Promise<string> {
@ -83,18 +95,36 @@ class FSPluginClient extends PluginClient {
} }
async watch(path: string): Promise<void> { async watch(path: string): Promise<void> {
console.log('watch', path) if (this.watcher) this.watcher.close()
if(this.watcher) this.watcher.close()
this.watcher = this.watcher =
chokidar.watch(path).on('change', (path, stats) => { chokidar.watch(this.fixPath(path)).on('change', (path, stats) => {
console.log('change', path, stats) console.log('change', path, stats)
this.emit('change', path, stats) this.emit('change', path, stats)
}) })
} }
async closeWatch(): Promise<void> { async closeWatch(): Promise<void> {
console.log('closeWatch') console.log('closing Watcher', this.webContentsId)
if(this.watcher) this.watcher.close() if (this.watcher) this.watcher.close()
}
async setWorkingDir(): Promise<void> {
const dirs = dialog.showOpenDialogSync(this.window, {
properties: ['openDirectory']
})
if (dirs && dirs.length > 0)
this.workingDir = dirs[0]
this.emit('workingDirChanged', dirs[0])
}
fixPath(path: string): string {
if (path.startsWith('/')) {
path = path.slice(1)
}
path = this.workingDir + path
return path
} }

@ -1,8 +1,7 @@
import { Plugin } from "@remixproject/engine";
import { PluginClient } from "@remixproject/plugin"; import { PluginClient } from "@remixproject/plugin";
import { Profile } from "@remixproject/plugin-utils"; import { Profile } from "@remixproject/plugin-utils";
import { spawn } from "child_process"; import { spawn } from "child_process";
import { createElectronClient } from "./lib/electronPluginClient"; import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin";
const profile: Profile = { const profile: Profile = {
name: 'git', name: 'git',
@ -10,23 +9,34 @@ const profile: Profile = {
description: 'Git plugin', description: 'Git plugin',
} }
export class GitPlugin extends Plugin { export class GitPlugin extends ElectronBasePlugin {
client: PluginClient client: PluginClient
constructor() { constructor() {
super(profile) super(profile)
} }
onActivation(): void { async createClient(webContentsId: number): Promise<void> {
this.client = new GitPluginClient() this.clients.push(new GitPluginClient(webContentsId))
} }
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
}
} }
class GitPluginClient extends PluginClient { const clientProfile: Profile = {
constructor() { name: 'git',
super() displayName: 'Git',
this.methods = ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version'] description: 'Git plugin',
createElectronClient(this, profile) methods: ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version']
}
class GitPluginClient extends ElectronBasePluginClient {
constructor(webContentsId: number) {
super(webContentsId, clientProfile)
this.onload(() => { this.onload(() => {
console.log('GitPluginClient onload') console.log('GitPluginClient onload')
}) })

@ -0,0 +1,43 @@
import { Plugin } from "@remixproject/engine";
import { PluginClient } from "@remixproject/plugin";
import { Profile } from "@remixproject/plugin-utils";
import { BrowserWindow } from "electron";
import { createElectronClient } from "./electronPluginClient";
export interface ElectronBasePluginInterface {
createClient(windowId: number): Promise<void>;
closeClient(windowId: number): Promise<void>;
}
export abstract class ElectronBasePlugin extends Plugin implements ElectronBasePluginInterface {
clients: ElectronBasePluginClient[] = [];
constructor(profile: Profile) {
super(profile);
this.methods = ['createClient', 'closeClient'];
}
async createClient(windowId: number): Promise<void> {
console.log('createClient method not implemented');
}
async closeClient(windowId: number): Promise<void> {
console.log('closeClient method not implemented');
}
}
export class ElectronBasePluginClient extends PluginClient {
window: Electron.BrowserWindow;
webContentsId: number;
constructor(webcontentsid: number, profile: Profile, methods: string[] = []) {
super();
console.log('ElectronBasePluginClient', profile);
this.methods = profile.methods;
this.webContentsId = webcontentsid;
BrowserWindow.getAllWindows().forEach((window) => {
if (window.webContents.id === webcontentsid) {
this.window = window;
}
});
createElectronClient(this, profile, this.window);
}
}

@ -2,21 +2,20 @@ import { ClientConnector, connectClient, applyApi, Client, PluginClient } from '
import type { Message, Api, ApiMap, Profile } from '@remixproject/plugin-utils' import type { Message, Api, ApiMap, Profile } from '@remixproject/plugin-utils'
import { IRemixApi } from '@remixproject/plugin-api' import { IRemixApi } from '@remixproject/plugin-api'
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { mainWindow } from '../..'
export class ElectronPluginClientConnector implements ClientConnector { export class ElectronPluginClientConnector implements ClientConnector {
constructor(public profile: Profile) { constructor(public profile: Profile, public browserWindow: Electron.BrowserWindow) {
} }
/** Send a message to the engine */ /** Send a message to the engine */
send(message: Partial<Message>) { send(message: Partial<Message>) {
mainWindow.webContents.send(this.profile.name + ':send', message) this.browserWindow.webContents.send(this.profile.name + ':send', message)
} }
/** Listen to message from the engine */ /** Listen to message from the engine */
on(cb: (message: Partial<Message>) => void) { on(cb: (message: Partial<Message>) => void) {
ipcMain.on(this.profile.name + ':on', (event, message) => { ipcMain.on(this.profile.name + ':on:' + this.browserWindow.webContents.id, (event, message) => {
cb(message) cb(message)
}) })
} }
@ -25,9 +24,11 @@ export class ElectronPluginClientConnector implements ClientConnector {
export const createElectronClient = < export const createElectronClient = <
P extends Api, P extends Api,
App extends ApiMap = Readonly<IRemixApi> App extends ApiMap = Readonly<IRemixApi>
>(client: PluginClient<P, App> = new PluginClient(), profile: Profile): Client<P, App> => { >(client: PluginClient<P, App> = new PluginClient(), profile: Profile
, window: Electron.BrowserWindow
): Client<P, App> => {
const c = client as any const c = client as any
connectClient(new ElectronPluginClientConnector(profile), c) connectClient(new ElectronPluginClientConnector(profile, window), c)
applyApi(c) applyApi(c)
return c return c
} }

@ -0,0 +1,18 @@
import {app, Menu, BrowserWindow} from 'electron';
import { createWindow } from '../../';
const commands: Record<string, (focusedWindow?: BrowserWindow) => void> = {
'window:new': () => {
// If window is created on the same tick, it will consume event too
setTimeout(createWindow, 0);
},
};
export const execCommand = (command: string, focusedWindow?: BrowserWindow) => {
const fn = commands[command];
if (fn) {
fn(focusedWindow);
}
};

@ -0,0 +1,36 @@
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
): MenuItemConstructorOptions => {
const isMac = process.platform === 'darwin';
return {
label: isMac ? 'Shell' : 'File',
submenu: [
{
label: 'New Window',
accelerator: commandKeys['window:new'],
click(item, focusedWindow) {
execCommand('window:new', focusedWindow);
}
},
{
type: 'separator'
},
{
label: 'Close',
accelerator: commandKeys['pane:close'],
click(item, focusedWindow) {
execCommand('pane:close', focusedWindow);
}
},
{
label: isMac ? 'Close Window' : 'Quit',
role: 'close',
accelerator: commandKeys['window:close']
}
]
};
};

@ -6,6 +6,7 @@ import { createElectronClient } from "./lib/electronPluginClient";
import os from 'os'; import os from 'os';
import * as pty from "node-pty" import * as pty from "node-pty"
import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin";
const profile: Profile = { const profile: Profile = {
name: 'xterm', name: 'xterm',
@ -13,42 +14,52 @@ const profile: Profile = {
description: 'xterm plugin', description: 'xterm plugin',
} }
export class XtermPlugin extends Plugin { export class XtermPlugin extends ElectronBasePlugin {
client: PluginClient client: PluginClient
constructor() { constructor() {
super(profile) super(profile)
} }
onActivation(): void { async createClient(webContentsId: number): Promise<void> {
this.client = new XtermPluginClient() console.log('createClient', webContentsId)
this.clients.push(new XtermPluginClient(webContentsId))
} }
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
}
}
const clientProfile: Profile = {
name: 'xterm',
displayName: 'xterm',
description: 'xterm plugin',
methods: ['createTerminal', 'close', 'keystroke']
} }
class XtermPluginClient extends PluginClient { class XtermPluginClient extends ElectronBasePluginClient {
terminals: pty.IPty[] = [] terminals: pty.IPty[] = []
constructor() { constructor(webContentsId: number) {
super() super(webContentsId, clientProfile)
this.methods = ['keystroke', 'createTerminal', 'close']
createElectronClient(this, profile)
this.onload(() => { this.onload(() => {
console.log('XtermPluginClient onload') console.log('XtermPluginClient onload')
}) })
} }
async keystroke(key: string, pid: number): Promise<void> { async keystroke(key: string, pid: number): Promise<void> {
console.log('keystroke', key)
this.terminals[pid].write(key) this.terminals[pid].write(key)
} }
async createTerminal(path?: string): Promise<number>{ async createTerminal(path?: string): Promise<number> {
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
const ptyProcess = pty.spawn(shell, [], { const ptyProcess = pty.spawn(shell, [], {
name: 'xterm-color', name: 'xterm-color',
cols: 80, cols: 80,
rows: 30, rows: 30,
cwd: path || process.cwd() , cwd: path || process.cwd(),
env: process.env env: process.env
}); });
@ -56,17 +67,17 @@ class XtermPluginClient extends PluginClient {
this.sendData(data, ptyProcess.pid); this.sendData(data, ptyProcess.pid);
}) })
this.terminals[ptyProcess.pid] = ptyProcess this.terminals[ptyProcess.pid] = ptyProcess
console.log('create terminal', ptyProcess.pid)
return ptyProcess.pid return ptyProcess.pid
} }
async close(pid: number): Promise<void>{ async close(pid: number): Promise<void> {
this.terminals[pid].kill() this.terminals[pid].kill()
delete this.terminals[pid] delete this.terminals[pid]
this.emit('close', pid) this.emit('close', pid)
} }
async sendData(data: string, pid: number){ async sendData(data: string, pid: number) {
this.emit('data', data, pid) this.emit('data', data, pid)
} }

@ -5,6 +5,7 @@ export interface IElectronAPI {
on: (cb: any) => void on: (cb: any) => void
send: (message: Partial<Message>) => void send: (message: Partial<Message>) => void
}[] }[]
getWindowId: () => string | undefined
} }
declare global { declare global {

@ -1,6 +1,7 @@
import { app, BrowserWindow } from 'electron'; import { app, BrowserWindow, Menu } from 'electron';
import path from 'path'; import path from 'path';
import fixPath from 'fix-path'; import fixPath from 'fix-path';
import { add } from 'lodash';
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production). // whether you're running in development or production).
@ -14,22 +15,29 @@ if (require('electron-squirrel-startup')) {
app.quit(); app.quit();
} }
export let mainWindow: BrowserWindow; export let mainWindow: BrowserWindow;
const createWindow = (): void => { export const createWindow = (): void => {
// generate unique id for this window
const id = Date.now().toString();
// Create the browser window. // Create the browser window.
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
height: 800, height: 800,
width: 1024, width: 1024,
webPreferences: { webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
additionalArguments: [`--window-id=${id}`],
}, },
}); });
// and load the index.html of the app. // and load the index.html of the app.
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
mainWindow.maximize(); mainWindow.maximize();
// Open the DevTools. // Open the DevTools.
//mainWindow.webContents.openDevTools(); //mainWindow.webContents.openDevTools();
BrowserWindow.getAllWindows().forEach(window => {
console.log('window IDS created', window.webContents.id)
})
require('./electron/engine') require('./electron/engine')
}; };
@ -38,6 +46,13 @@ const createWindow = (): void => {
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
app.on('ready', createWindow); app.on('ready', createWindow);
// when a window is closed event
app.on('web-contents-created', (event, contents) => {
console.log('web-contents-created', contents.id)
})
// Quit when all windows are closed, except on macOS. There, it's common // Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits // for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q. // explicitly with Cmd + Q.
@ -56,4 +71,28 @@ app.on('activate', () => {
}); });
// In this file you can include the rest of your app's specific main process // In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here. // code. You can also put them in separate files and import them here
const isMac = process.platform === 'darwin'
import shellMenu from './electron/menus/shell';
import { execCommand } from './electron/menus/commands';
const commandKeys: Record<string, string> = {
'tab:new': 'CmdOrCtrl+T',
'window:new': 'CmdOrCtrl+N',
'pane:splitDown': 'CmdOrCtrl+D',
'pane:splitRight': 'CmdOrCtrl+E',
'pane:close': 'CmdOrCtrl+W',
'window:close': 'CmdOrCtrl+Q',
};
const menu = [shellMenu(commandKeys, execCommand)]
Menu.setApplicationMenu(Menu.buildFromTemplate(menu))
//const menu = Menu.buildFromTemplate(shellMenu([], undefined))
//Menu.setApplicationMenu(menu)

@ -8,16 +8,36 @@ console.log('preload.ts')
const exposedPLugins = ['fs', 'git', 'xterm'] const exposedPLugins = ['fs', 'git', 'xterm']
console.log('preload.ts', process)
let webContentsId: number | undefined
/*
// get the window id from the process arguments
const windowIdFromArgs = process.argv.find(arg => arg.startsWith('--window-id='))
if (windowIdFromArgs) {
[, windowId] = windowIdFromArgs.split('=')
console.log('windowId', windowId, )
}
*/
ipcRenderer.invoke('getWebContentsID').then((id: number) => {
webContentsId = id
console.log('getWebContentsID', webContentsId)
})
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
activatePlugin: (name: string) => { activatePlugin: (name: string) => {
return ipcRenderer.invoke('manager:activatePlugin', name) return ipcRenderer.invoke('manager:activatePlugin', name)
}, },
getWindowId: () => ipcRenderer.invoke('getWindowID'),
plugins: exposedPLugins.map(name => { plugins: exposedPLugins.map(name => {
return { return {
name, name,
on: (cb:any) => ipcRenderer.on(`${name}:send`, cb), on: (cb:any) => ipcRenderer.on(`${name}:send`, cb),
send: (message: Partial<Message>) => ipcRenderer.send(`${name}:on`, message) send: (message: Partial<Message>) => {
console.log('send', message, `${name}:on:${webContentsId}`)
ipcRenderer.send(`${name}:on:${webContentsId}`, message)
}
} }
}) })
}) })

@ -1,14 +1,97 @@
import { ElectronPlugin } from './lib/electronPlugin'; import { ElectronPlugin } from './lib/electronPlugin';
let workingDir = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/'
const fixPath = (path: string) => {
/*
// if it starts with /, it's an absolute path remove it
if (path.startsWith('/')) {
path = path.slice(1)
}
path = workingDir + path
*/
return path
}
export class fsPlugin extends ElectronPlugin { export class fsPlugin extends ElectronPlugin {
public fs: any
constructor(){ constructor() {
super({ super({
displayName: 'fs', displayName: 'fs',
name: 'fs', name: 'fs',
description: 'fs', description: 'fs',
}) })
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists'] this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'setWorkingDir']
this.fs = {
exists: async (path: string) => {
path = fixPath(path)
const exists = await this.call('fs', 'exists', path)
return exists
},
rmdir: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'rmdir', path)
},
readdir: async (path: string) => {
path = fixPath(path)
console.log('readdir', path)
const files = await this.call('fs', 'readdir', path)
return files
},
unlink: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'unlink', path)
},
mkdir: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'mkdir', path)
},
readFile: async (path: string) => {
path = fixPath(path)
return await this.call('fs', 'readFile', path)
}
,
rename: async (from: string, to: string) => {
return await this.call('fs', 'rename', from, to)
},
writeFile: async (path: string, content: string) => {
path = fixPath(path)
return await this.call('fs', 'writeFile', path, content)
}
,
stat: async (path: string) => {
path = fixPath(path)
const stat = await this.call('fs', 'stat', path)
stat.isDirectory = () => stat.isDirectoryValue
stat.isFile = () => !stat.isDirectoryValue
//console.log('stat', path, stat)
return stat
}
}
}
async onActivation() {
console.log('fsPluginClient onload', this.fs);
(window as any).remixFileSystem = this.fs
this.on('fs', 'workingDirChanged', (path: string) => {
console.log('change working dir', path)
workingDir = path
})
} }
} }

@ -9,6 +9,7 @@ export abstract class ElectronPlugin extends Plugin {
send: (message: Partial<Message>) => void send: (message: Partial<Message>) => void
on: (cb: (event: any, message: any) => void) => void on: (cb: (event: any, message: any) => void) => void
} }
profile: Profile profile: Profile
constructor(profile: Profile) { constructor(profile: Profile) {
super(profile) super(profile)
@ -29,6 +30,8 @@ export abstract class ElectronPlugin extends Plugin {
this.api.on((event: any, message: any) => { this.api.on((event: any, message: any) => {
this.getMessage(message) this.getMessage(message)
}) })
} }
/** /**
@ -50,7 +53,7 @@ export abstract class ElectronPlugin extends Plugin {
} }
/** Close connection with the plugin */ /** Close connection with the plugin */
protected disconnect(): any | Promise<any> { protected disconnect(): any | Promise<any> {
// TODO: Disconnect from the plugin
} }
async activate() { async activate() {

@ -0,0 +1,40 @@
import { Plugin } from "@remixproject/engine"
import { useEffect, useState } from "react"
import { ElectronPlugin } from "../lib/electronPlugin"
interface RemixUIFileDialogInterface {
plugin: Plugin
}
export const RemixUIFileDialog = (props: RemixUIFileDialogInterface) => {
const { plugin } = props
const [files, setFiles] = useState<string[]>([])
const [workingDir, setWorkingDir] = useState<string>('')
useEffect(() => {
plugin.on('fs', 'workingDirChanged', async (path: string) => {
console.log('workingDirChanged')
setWorkingDir(path)
await readdir()
})
}, [])
const readdir = async () => {
const files = await plugin.call('fs', 'readdir', '/')
console.log('files', files)
setFiles(files)
}
return (
<>
<h1>RemixUIFileDialog</h1>
<button onClick={() => plugin.call('fs', 'setWorkingDir')}>open</button>
<button onClick={async () => await readdir()}>read</button>
<hr></hr>
{workingDir}
<hr></hr>
{files.map(file => <div key={file}>{file}</div>)}
</>
)
}

@ -7,13 +7,12 @@ export interface RemixUiXtermProps {
plugin: ElectronPlugin plugin: ElectronPlugin
pid: number pid: number
send: (data: string, pid: number) => void send: (data: string, pid: number) => void
data: string
timeStamp: number timeStamp: number
setTerminalRef: (pid: number, ref: any) => void setTerminalRef: (pid: number, ref: any) => void
} }
const RemixUiXterm = (props: RemixUiXtermProps) => { const RemixUiXterm = (props: RemixUiXtermProps) => {
const { plugin, pid, send, data, timeStamp } = props const { plugin, pid, send, timeStamp } = props
const xtermRef = React.useRef(null) const xtermRef = React.useRef(null)
useEffect(() => { useEffect(() => {

@ -8,13 +8,14 @@ export interface RemixUiXterminalsProps {
export interface xtermState { export interface xtermState {
pid: number pid: number
data: string queue: string
timeStamp: number timeStamp: number
ref: any ref: any
} }
export const RemixUiXterminals = (props: RemixUiXterminalsProps) => { export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
const [terminals, setTerminals] = useState<xtermState[]>([]) // eslint-disable-line const [terminals, setTerminals] = useState<xtermState[]>([])
const [workingDir, setWorkingDir] = useState<string>('')
const { plugin } = props const { plugin } = props
useEffect(() => { useEffect(() => {
@ -29,6 +30,10 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
return prevState.filter(xtermState => xtermState.pid !== pid) return prevState.filter(xtermState => xtermState.pid !== pid)
}) })
}) })
plugin.on('fs', 'workingDirChanged', (path: string) => {
setWorkingDir(path)
})
}, []) }, [])
const writeToTerminal = (data: string, pid: number) => { const writeToTerminal = (data: string, pid: number) => {
@ -36,6 +41,8 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
const terminal = prevState.find(xtermState => xtermState.pid === pid) const terminal = prevState.find(xtermState => xtermState.pid === pid)
if (terminal.ref && terminal.ref.terminal) { if (terminal.ref && terminal.ref.terminal) {
terminal.ref.terminal.write(data) terminal.ref.terminal.write(data)
}else {
terminal.queue += data
} }
return [...prevState] return [...prevState]
}) })
@ -51,12 +58,12 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
} }
const createTerminal = async () => { const createTerminal = async () => {
const pid = await plugin.call('xterm', 'createTerminal') const pid = await plugin.call('xterm', 'createTerminal', workingDir)
setTerminals(prevState => { setTerminals(prevState => {
return [...prevState, { return [...prevState, {
pid: pid, pid: pid,
data: '', queue: '',
timeStamp: Date.now(), timeStamp: Date.now(),
ref: null ref: null
}] }]
@ -64,8 +71,14 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
} }
const setTerminalRef = (pid: number, ref: any) => { const setTerminalRef = (pid: number, ref: any) => {
console.log('setTerminalRef', pid, ref)
setTerminals(prevState => { setTerminals(prevState => {
prevState.find(xtermState => xtermState.pid === pid).ref = ref const terminal = prevState.find(xtermState => xtermState.pid === pid)
terminal.ref = ref
if(terminal.queue) {
ref.terminal.write(terminal.queue)
terminal.queue = ''
}
return [...prevState] return [...prevState]
}) })
} }
@ -79,7 +92,7 @@ export const RemixUiXterminals = (props: RemixUiXterminalsProps) => {
{terminals.map((xtermState) => { {terminals.map((xtermState) => {
return ( return (
<div key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}>{xtermState.pid} <div key={xtermState.pid} data-id={`remixUIXT${xtermState.pid}`}>{xtermState.pid}
<RemixUiXterm setTerminalRef={setTerminalRef} timeStamp={xtermState.timeStamp} send={send} pid={xtermState.pid} data={xtermState.data} plugin={plugin}></RemixUiXterm> <RemixUiXterm setTerminalRef={setTerminalRef} timeStamp={xtermState.timeStamp} send={send} pid={xtermState.pid} plugin={plugin}></RemixUiXterm>
</div> </div>
) )
})} })}

@ -1,7 +1,6 @@
import { ElectronPlugin } from './lib/electronPlugin'; import { ElectronPlugin } from './lib/electronPlugin';
export class xtermPlugin extends ElectronPlugin { export class xtermPlugin extends ElectronPlugin {
constructor(){ constructor(){
super({ super({
displayName: 'xterm', displayName: 'xterm',
@ -9,5 +8,4 @@ export class xtermPlugin extends ElectronPlugin {
description: 'xterm', description: 'xterm',
}) })
} }
} }

@ -1,5 +1,5 @@
import { Engine, PluginManager } from '@remixproject/engine'; import { Engine, Plugin, PluginManager } from '@remixproject/engine';
import { fsPlugin } from './remix/fsPlugin'; import { fsPlugin } from './remix/fsPlugin';
import { gitPlugin } from './remix/gitPlugin'; import { gitPlugin } from './remix/gitPlugin';
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
@ -70,16 +70,31 @@ class MyAppManager extends PluginManager {
const engine = new Engine() const engine = new Engine()
const appManager = new MyAppManager() const appManager = new MyAppManager()
const fs = new fsPlugin() export const fs = new fsPlugin()
const git = new gitPlugin() const git = new gitPlugin()
export const xterm = new xtermPlugin() export const xterm = new xtermPlugin()
export class filePanelPlugin extends Plugin {
constructor() {
super({
displayName: 'filePanel',
name: 'filePanel',
})
}
}
export const filePanel = new filePanelPlugin()
engine.register(appManager) engine.register(appManager)
engine.register(fs) engine.register(fs)
engine.register(git) engine.register(git)
engine.register(xterm) engine.register(xterm)
engine.register(filePanel)
appManager.activatePlugin('fs') appManager.activatePlugin('fs')
appManager.activatePlugin('git') appManager.activatePlugin('git')
appManager.activatePlugin('xterm') appManager.activatePlugin('xterm')
appManager.activatePlugin('filePanel')
setTimeout(async () => { setTimeout(async () => {
@ -90,4 +105,5 @@ setTimeout(async () => {
import './app' import './app'
import { ElectronPlugin } from './remix/lib/electronPlugin';

Loading…
Cancel
Save