update test app

rdesktop
filip mertens 1 year ago
parent 24039ebb43
commit 6c25d81166
  1. 10
      apps/1test/src/app.tsx
  2. 30
      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. 47
      apps/1test/src/electron/xtermPlugin.ts
  10. 1
      apps/1test/src/global.d.ts
  11. 49
      apps/1test/src/index.ts
  12. 24
      apps/1test/src/preload.ts
  13. 89
      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 { 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';
const container = document.getElementById('app');
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 { ipcMain } from 'electron';
import { BrowserWindow, ipcMain } from 'electron';
import { FSPlugin } from './fsPlugin';
import { GitPlugin } from './gitPlugin';
import { app } from 'electron';
import { XtermPlugin } from './xtermPlugin';
const engine = new Engine()
const appManager = new PluginManager()
const fsPlugin = new FSPlugin()
const gitPlugin = new GitPlugin()
const xtermPlugin = new XtermPlugin()
engine.register(appManager)
engine.register(fsPlugin)
engine.register(gitPlugin)
engine.register(xtermPlugin)
ipcMain.handle('manager:activatePlugin', async (event, arg) => {
console.log('manager:activatePlugin', arg)
if(await appManager.isActive(arg)){
return true
}
return await appManager.activatePlugin(arg)
engine.register(xtermPlugin)
appManager.activatePlugin('fs')
appManager.activatePlugin('git')
appManager.activatePlugin('xterm')
ipcMain.handle('manager:activatePlugin', async (event, plugin) => {
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 () => {
console.log('before-quit')
await appManager.call('fs', 'closeWatch')
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 { Stats } from "fs";
import { Profile } from "@remixproject/plugin-utils";
import chokidar from 'chokidar'
import { ElectronBasePlugin, ElectronBasePluginClient, ElectronBasePluginInterface } from "./lib/electronBasePlugin";
import { dialog } from 'electron';
const profile: Profile = {
displayName: 'fs',
name: 'fs',
description: 'fs',
description: 'fs'
}
export class FSPlugin extends Plugin {
client: FSPluginClient
export class FSPlugin extends ElectronBasePlugin {
clients: FSPluginClient[] = []
constructor() {
super(profile)
this.methods = ['closeWatch']
this.methods = [...super.methods, 'closeWatch']
}
async createClient(webContentsId: number): Promise<void> {
this.clients.push(new FSPluginClient(webContentsId))
}
onActivation(): void {
this.client = new FSPluginClient()
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
}
async closeWatch(): Promise<void> {
console.log('closeWatch')
await this.client.closeWatch()
for (const client of this.clients) {
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
constructor() {
super()
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'watch', 'closeWatch', 'currentPath']
createElectronClient(this, profile)
workingDir: string = '/Volumes/bunsen/code/rmproject2/remix-project/apps/remix-ide/contracts/'
constructor(webContentsId: number) {
super(webContentsId, clientProfile)
this.onload(() => {
console.log('fsPluginClient onload')
})
@ -43,39 +55,39 @@ class FSPluginClient extends PluginClient {
async readdir(path: string): Promise<string[]> {
// call node fs.readdir
return fs.readdir(path)
return fs.readdir(this.fixPath(path))
}
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> {
return fs.writeFile(path, content, 'utf8')
return fs.writeFile(this.fixPath(path), content, 'utf8')
}
async mkdir(path: string): Promise<void> {
return fs.mkdir(path)
return fs.mkdir(this.fixPath(path))
}
async rmdir(path: string): Promise<void> {
return fs.rmdir(path)
return fs.rmdir(this.fixPath(path))
}
async unlink(path: string): Promise<void> {
return fs.unlink(path)
return fs.unlink(this.fixPath(path))
}
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> {
return fs.stat(path)
return fs.stat(this.fixPath(path))
}
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> {
@ -83,18 +95,36 @@ class FSPluginClient extends PluginClient {
}
async watch(path: string): Promise<void> {
console.log('watch', path)
if(this.watcher) this.watcher.close()
if (this.watcher) this.watcher.close()
this.watcher =
chokidar.watch(path).on('change', (path, stats) => {
chokidar.watch(this.fixPath(path)).on('change', (path, stats) => {
console.log('change', path, stats)
this.emit('change', path, stats)
})
}
async closeWatch(): Promise<void> {
console.log('closeWatch')
if(this.watcher) this.watcher.close()
console.log('closing Watcher', this.webContentsId)
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 { Profile } from "@remixproject/plugin-utils";
import { spawn } from "child_process";
import { createElectronClient } from "./lib/electronPluginClient";
import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin";
const profile: Profile = {
name: 'git',
@ -10,23 +9,34 @@ const profile: Profile = {
description: 'Git plugin',
}
export class GitPlugin extends Plugin {
export class GitPlugin extends ElectronBasePlugin {
client: PluginClient
constructor() {
super(profile)
}
onActivation(): void {
this.client = new GitPluginClient()
async createClient(webContentsId: number): Promise<void> {
this.clients.push(new GitPluginClient(webContentsId))
}
async closeClient(webContentsId: number): Promise<void> {
console.log('closeClient', webContentsId)
}
}
class GitPluginClient extends PluginClient {
constructor() {
super()
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']
createElectronClient(this, profile)
const clientProfile: Profile = {
name: 'git',
displayName: 'Git',
description: 'Git plugin',
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(() => {
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 { IRemixApi } from '@remixproject/plugin-api'
import { ipcMain } from 'electron'
import { mainWindow } from '../..'
export class ElectronPluginClientConnector implements ClientConnector {
constructor(public profile: Profile) {
constructor(public profile: Profile, public browserWindow: Electron.BrowserWindow) {
}
/** Send a message to the engine */
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 */
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)
})
}
@ -25,9 +24,11 @@ export class ElectronPluginClientConnector implements ClientConnector {
export const createElectronClient = <
P extends Api,
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
connectClient(new ElectronPluginClientConnector(profile), c)
connectClient(new ElectronPluginClientConnector(profile, window), c)
applyApi(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 * as pty from "node-pty"
import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin";
const profile: Profile = {
name: 'xterm',
@ -13,60 +14,70 @@ const profile: Profile = {
description: 'xterm plugin',
}
export class XtermPlugin extends Plugin {
export class XtermPlugin extends ElectronBasePlugin {
client: PluginClient
constructor() {
super(profile)
}
onActivation(): void {
this.client = new XtermPluginClient()
async createClient(webContentsId: number): Promise<void> {
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[] = []
constructor() {
super()
this.methods = ['keystroke', 'createTerminal', 'close']
createElectronClient(this, profile)
constructor(webContentsId: number) {
super(webContentsId, clientProfile)
this.onload(() => {
console.log('XtermPluginClient onload')
})
}
async keystroke(key: string, pid: number): Promise<void> {
console.log('keystroke', 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 ptyProcess = pty.spawn(shell, [], {
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: path || process.cwd() ,
env: process.env
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: path || process.cwd(),
env: process.env
});
ptyProcess.onData((data: string) => {
this.sendData(data, ptyProcess.pid);
})
this.terminals[ptyProcess.pid] = ptyProcess
console.log('create terminal', ptyProcess.pid)
return ptyProcess.pid
}
async close(pid: number): Promise<void>{
async close(pid: number): Promise<void> {
this.terminals[pid].kill()
delete this.terminals[pid]
this.emit('close', pid)
}
async sendData(data: string, pid: number){
async sendData(data: string, pid: number) {
this.emit('data', data, pid)
}

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

@ -1,6 +1,7 @@
import { app, BrowserWindow } from 'electron';
import { app, BrowserWindow, Menu } from 'electron';
import path from '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
// 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).
@ -14,22 +15,29 @@ if (require('electron-squirrel-startup')) {
app.quit();
}
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.
mainWindow = new BrowserWindow({
height: 800,
width: 1024,
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.
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
mainWindow.maximize();
// Open the DevTools.
//mainWindow.webContents.openDevTools();
BrowserWindow.getAllWindows().forEach(window => {
console.log('window IDS created', window.webContents.id)
})
require('./electron/engine')
};
@ -38,6 +46,13 @@ const createWindow = (): void => {
// Some APIs can only be used after this event occurs.
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
// for applications and their menu bar to stay active until the user quits
// 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
// 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']
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', {
activatePlugin: (name: string) => {
return ipcRenderer.invoke('manager:activatePlugin', name)
},
getWindowId: () => ipcRenderer.invoke('getWindowID'),
plugins: exposedPLugins.map(name => {
return {
name,
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';
export class fsPlugin extends 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
*/
constructor(){
return path
}
export class fsPlugin extends ElectronPlugin {
public fs: any
constructor() {
super({
displayName: 'fs',
name: '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
on: (cb: (event: any, message: any) => void) => void
}
profile: Profile
constructor(profile: Profile) {
super(profile)
@ -29,6 +30,8 @@ export abstract class ElectronPlugin extends Plugin {
this.api.on((event: any, message: any) => {
this.getMessage(message)
})
}
/**
@ -50,7 +53,7 @@ export abstract class ElectronPlugin extends Plugin {
}
/** Close connection with the plugin */
protected disconnect(): any | Promise<any> {
// TODO: Disconnect from the plugin
}
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
pid: number
send: (data: string, pid: number) => void
data: string
timeStamp: number
setTerminalRef: (pid: number, ref: any) => void
}
const RemixUiXterm = (props: RemixUiXtermProps) => {
const { plugin, pid, send, data, timeStamp } = props
const { plugin, pid, send, timeStamp } = props
const xtermRef = React.useRef(null)
useEffect(() => {

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

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

Loading…
Cancel
Save