From 6c25d81166aa15afec911e5e01ffff633682215a Mon Sep 17 00:00:00 2001 From: filip mertens Date: Wed, 7 Jun 2023 11:49:03 +0200 Subject: [PATCH] update test app --- apps/1test/src/app.tsx | 10 ++- apps/1test/src/electron/engine.ts | 30 ++++--- apps/1test/src/electron/fsPlugin.ts | 90 ++++++++++++------- apps/1test/src/electron/gitPlugin.ts | 30 ++++--- .../src/electron/lib/electronBasePlugin.ts | 43 +++++++++ .../src/electron/lib/electronPluginClient.ts | 13 +-- apps/1test/src/electron/menus/commands.ts | 18 ++++ apps/1test/src/electron/menus/shell.ts | 36 ++++++++ apps/1test/src/electron/xtermPlugin.ts | 47 ++++++---- apps/1test/src/global.d.ts | 1 + apps/1test/src/index.ts | 49 ++++++++-- apps/1test/src/preload.ts | 24 ++++- apps/1test/src/remix/fsPlugin.ts | 89 +++++++++++++++++- apps/1test/src/remix/lib/electronPlugin.ts | 5 +- .../src/remix/ui/remix-ui-filedialog.tsx | 40 +++++++++ apps/1test/src/remix/ui/remix-ui-xterm.tsx | 3 +- .../src/remix/ui/remix-ui-xterminals.tsx | 25 ++++-- apps/1test/src/remix/xtermPlugin.ts | 2 - apps/1test/src/renderer.ts | 20 ++++- 19 files changed, 476 insertions(+), 99 deletions(-) create mode 100644 apps/1test/src/electron/lib/electronBasePlugin.ts create mode 100644 apps/1test/src/electron/menus/commands.ts create mode 100644 apps/1test/src/electron/menus/shell.ts create mode 100644 apps/1test/src/remix/ui/remix-ui-filedialog.tsx diff --git a/apps/1test/src/app.tsx b/apps/1test/src/app.tsx index 1ac31ebff3..4603074530 100644 --- a/apps/1test/src/app.tsx +++ b/apps/1test/src/app.tsx @@ -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() \ No newline at end of file +root.render( + <> + + + +) \ No newline at end of file diff --git a/apps/1test/src/electron/engine.ts b/apps/1test/src/electron/engine.ts index 9a27037d47..96cf0773d9 100644 --- a/apps/1test/src/electron/engine.ts +++ b/apps/1test/src/electron/engine.ts @@ -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() -}) \ No newline at end of file +}) + diff --git a/apps/1test/src/electron/fsPlugin.ts b/apps/1test/src/electron/fsPlugin.ts index e82b21285b..bc592c507e 100644 --- a/apps/1test/src/electron/fsPlugin.ts +++ b/apps/1test/src/electron/fsPlugin.ts @@ -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 { + this.clients.push(new FSPluginClient(webContentsId)) } - onActivation(): void { - this.client = new FSPluginClient() + async closeClient(webContentsId: number): Promise { + console.log('closeClient', webContentsId) } + async closeWatch(): Promise { - 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 { // call node fs.readdir - return fs.readdir(path) + return fs.readdir(this.fixPath(path)) } async readFile(path: string): Promise { - return fs.readFile(path, 'utf8') + return fs.readFile(this.fixPath(path), 'utf8') } async writeFile(path: string, content: string): Promise { - return fs.writeFile(path, content, 'utf8') + return fs.writeFile(this.fixPath(path), content, 'utf8') } async mkdir(path: string): Promise { - return fs.mkdir(path) + return fs.mkdir(this.fixPath(path)) } async rmdir(path: string): Promise { - return fs.rmdir(path) + return fs.rmdir(this.fixPath(path)) } async unlink(path: string): Promise { - return fs.unlink(path) + return fs.unlink(this.fixPath(path)) } async rename(oldPath: string, newPath: string): Promise { - return fs.rename(oldPath, newPath) + return fs.rename(this.fixPath(oldPath), this.fixPath(newPath)) } async stat(path: string): Promise { - return fs.stat(path) + return fs.stat(this.fixPath(path)) } async exists(path: string): Promise { - return fs.access(path).then(() => true).catch(() => false) + return fs.access(this.fixPath(path)).then(() => true).catch(() => false) } async currentPath(): Promise { @@ -83,18 +95,36 @@ class FSPluginClient extends PluginClient { } async watch(path: string): Promise { - 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 { - console.log('closeWatch') - if(this.watcher) this.watcher.close() + console.log('closing Watcher', this.webContentsId) + if (this.watcher) this.watcher.close() + } + + async setWorkingDir(): Promise { + 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 } diff --git a/apps/1test/src/electron/gitPlugin.ts b/apps/1test/src/electron/gitPlugin.ts index ab2a68b491..65f635b0e8 100644 --- a/apps/1test/src/electron/gitPlugin.ts +++ b/apps/1test/src/electron/gitPlugin.ts @@ -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 { + this.clients.push(new GitPluginClient(webContentsId)) } + async closeClient(webContentsId: number): Promise { + 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') }) diff --git a/apps/1test/src/electron/lib/electronBasePlugin.ts b/apps/1test/src/electron/lib/electronBasePlugin.ts new file mode 100644 index 0000000000..d0a31103d9 --- /dev/null +++ b/apps/1test/src/electron/lib/electronBasePlugin.ts @@ -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; + closeClient(windowId: number): Promise; +} + +export abstract class ElectronBasePlugin extends Plugin implements ElectronBasePluginInterface { + clients: ElectronBasePluginClient[] = []; + constructor(profile: Profile) { + super(profile); + this.methods = ['createClient', 'closeClient']; + } + + async createClient(windowId: number): Promise { + console.log('createClient method not implemented'); + } + async closeClient(windowId: number): Promise { + 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); + } +} + diff --git a/apps/1test/src/electron/lib/electronPluginClient.ts b/apps/1test/src/electron/lib/electronPluginClient.ts index 6dd1c6d18e..f50ff0e8c2 100644 --- a/apps/1test/src/electron/lib/electronPluginClient.ts +++ b/apps/1test/src/electron/lib/electronPluginClient.ts @@ -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) { - 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) => 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 ->(client: PluginClient = new PluginClient(), profile: Profile): Client => { +>(client: PluginClient = new PluginClient(), profile: Profile +, window: Electron.BrowserWindow +): Client => { const c = client as any - connectClient(new ElectronPluginClientConnector(profile), c) + connectClient(new ElectronPluginClientConnector(profile, window), c) applyApi(c) return c } \ No newline at end of file diff --git a/apps/1test/src/electron/menus/commands.ts b/apps/1test/src/electron/menus/commands.ts new file mode 100644 index 0000000000..9656c5a03a --- /dev/null +++ b/apps/1test/src/electron/menus/commands.ts @@ -0,0 +1,18 @@ +import {app, Menu, BrowserWindow} from 'electron'; +import { createWindow } from '../../'; + +const commands: Record 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); + } +}; diff --git a/apps/1test/src/electron/menus/shell.ts b/apps/1test/src/electron/menus/shell.ts new file mode 100644 index 0000000000..051d0d2bbb --- /dev/null +++ b/apps/1test/src/electron/menus/shell.ts @@ -0,0 +1,36 @@ +import {BrowserWindow, MenuItemConstructorOptions} from 'electron'; + +export default ( + commandKeys: Record, + 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'] + } + ] + }; +}; diff --git a/apps/1test/src/electron/xtermPlugin.ts b/apps/1test/src/electron/xtermPlugin.ts index 20f735b61a..53db68dad9 100644 --- a/apps/1test/src/electron/xtermPlugin.ts +++ b/apps/1test/src/electron/xtermPlugin.ts @@ -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 { + console.log('createClient', webContentsId) + this.clients.push(new XtermPluginClient(webContentsId)) } + async closeClient(webContentsId: number): Promise { + 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 { - console.log('keystroke', key) this.terminals[pid].write(key) } - async createTerminal(path?: string): Promise{ + async createTerminal(path?: string): Promise { 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{ + async close(pid: number): Promise { 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) } diff --git a/apps/1test/src/global.d.ts b/apps/1test/src/global.d.ts index 672fea248b..7e2a37c821 100644 --- a/apps/1test/src/global.d.ts +++ b/apps/1test/src/global.d.ts @@ -5,6 +5,7 @@ export interface IElectronAPI { on: (cb: any) => void send: (message: Partial) => void }[] + getWindowId: () => string | undefined } declare global { diff --git a/apps/1test/src/index.ts b/apps/1test/src/index.ts index 73bb8edb47..584e062f92 100644 --- a/apps/1test/src/index.ts +++ b/apps/1test/src/index.ts @@ -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 = { + '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) diff --git a/apps/1test/src/preload.ts b/apps/1test/src/preload.ts index f216faaf5e..b9918327aa 100644 --- a/apps/1test/src/preload.ts +++ b/apps/1test/src/preload.ts @@ -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) => ipcRenderer.send(`${name}:on`, message) + send: (message: Partial) => { + console.log('send', message, `${name}:on:${webContentsId}`) + ipcRenderer.send(`${name}:on:${webContentsId}`, message) + } } }) }) \ No newline at end of file diff --git a/apps/1test/src/remix/fsPlugin.ts b/apps/1test/src/remix/fsPlugin.ts index b96672a1f8..4dc5954fe8 100644 --- a/apps/1test/src/remix/fsPlugin.ts +++ b/apps/1test/src/remix/fsPlugin.ts @@ -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 + }) } } \ No newline at end of file diff --git a/apps/1test/src/remix/lib/electronPlugin.ts b/apps/1test/src/remix/lib/electronPlugin.ts index 1286a4c2af..f797c8867f 100644 --- a/apps/1test/src/remix/lib/electronPlugin.ts +++ b/apps/1test/src/remix/lib/electronPlugin.ts @@ -9,6 +9,7 @@ export abstract class ElectronPlugin extends Plugin { send: (message: Partial) => 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 { - + // TODO: Disconnect from the plugin } async activate() { diff --git a/apps/1test/src/remix/ui/remix-ui-filedialog.tsx b/apps/1test/src/remix/ui/remix-ui-filedialog.tsx new file mode 100644 index 0000000000..ee71d5b2cb --- /dev/null +++ b/apps/1test/src/remix/ui/remix-ui-filedialog.tsx @@ -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([]) + const [workingDir, setWorkingDir] = useState('') + + 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 ( + <> +

RemixUIFileDialog

+ + +
+ {workingDir} +
+ + {files.map(file =>
{file}
)} + + ) +} \ No newline at end of file diff --git a/apps/1test/src/remix/ui/remix-ui-xterm.tsx b/apps/1test/src/remix/ui/remix-ui-xterm.tsx index 010ab39ddb..e75fffd1a9 100644 --- a/apps/1test/src/remix/ui/remix-ui-xterm.tsx +++ b/apps/1test/src/remix/ui/remix-ui-xterm.tsx @@ -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(() => { diff --git a/apps/1test/src/remix/ui/remix-ui-xterminals.tsx b/apps/1test/src/remix/ui/remix-ui-xterminals.tsx index 6b7d30149f..20a6cc9b1b 100644 --- a/apps/1test/src/remix/ui/remix-ui-xterminals.tsx +++ b/apps/1test/src/remix/ui/remix-ui-xterminals.tsx @@ -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([]) // eslint-disable-line + const [terminals, setTerminals] = useState([]) + const [workingDir, setWorkingDir] = useState('') 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 (
{xtermState.pid} - +
) })} diff --git a/apps/1test/src/remix/xtermPlugin.ts b/apps/1test/src/remix/xtermPlugin.ts index e1bb464fe4..bae81069b0 100644 --- a/apps/1test/src/remix/xtermPlugin.ts +++ b/apps/1test/src/remix/xtermPlugin.ts @@ -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', }) } - } \ No newline at end of file diff --git a/apps/1test/src/renderer.ts b/apps/1test/src/renderer.ts index 86061f9d98..d37801a377 100644 --- a/apps/1test/src/renderer.ts +++ b/apps/1test/src/renderer.ts @@ -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';