->(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';