parent
a2d511d445
commit
272bf13344
@ -0,0 +1,18 @@ |
||||
import { ElectronPlugin } from '@remixproject/engine-electron'; |
||||
|
||||
export class fsPlugin extends ElectronPlugin { |
||||
|
||||
constructor(){ |
||||
super({ |
||||
displayName: 'fs', |
||||
name: 'fs', |
||||
description: 'fs', |
||||
}) |
||||
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists'] |
||||
} |
||||
|
||||
async onActivation() { |
||||
console.log('fsPluginClient onload') |
||||
} |
||||
|
||||
} |
@ -1,9 +0,0 @@ |
||||
{ |
||||
"presets": ["@babel/preset-env", ["@babel/preset-react", |
||||
{"runtime": "automatic"} |
||||
]], |
||||
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", "@babel/plugin-proposal-nullish-coalescing-operator"], |
||||
"ignore": [ |
||||
"**/node_modules/**" |
||||
] |
||||
} |
@ -1,16 +0,0 @@ |
||||
# This file is used by: |
||||
# 1. autoprefixer to adjust CSS to support the below specified browsers |
||||
# 2. babel preset-env to adjust included polyfills |
||||
# |
||||
# For additional information regarding the format and rule options, please see: |
||||
# https://github.com/browserslist/browserslist#queries |
||||
# |
||||
# If you need to support different browsers in production, you may tweak the list below. |
||||
|
||||
last 1 Chrome version |
||||
last 1 Firefox version |
||||
last 2 Edge major versions |
||||
last 2 Safari major version |
||||
last 2 iOS major versions |
||||
Firefox ESR |
||||
not IE 9-11 # For IE 9-11 support, remove 'not'. |
@ -1,34 +0,0 @@ |
||||
{ |
||||
"extends": [ |
||||
"plugin:@nrwl/nx/react", |
||||
"../../.eslintrc.json" |
||||
], |
||||
"ignorePatterns": [ |
||||
"!**/*" |
||||
], |
||||
"overrides": [ |
||||
{ |
||||
"files": [ |
||||
"*.ts", |
||||
"*.tsx", |
||||
"*.js", |
||||
"*.jsx" |
||||
], |
||||
"rules": {} |
||||
}, |
||||
{ |
||||
"files": [ |
||||
"*.ts", |
||||
"*.tsx" |
||||
], |
||||
"rules": {} |
||||
}, |
||||
{ |
||||
"files": [ |
||||
"*.js", |
||||
"*.jsx" |
||||
], |
||||
"rules": {} |
||||
} |
||||
] |
||||
} |
@ -1,27 +1,44 @@ |
||||
{ |
||||
"name": "remixdesktop", |
||||
"productName": "remixdesktop", |
||||
"version": "1.0.0", |
||||
"description": "My Electron application description", |
||||
"main": ".webpack/main", |
||||
"scripts": { |
||||
"start": "electron-forge start", |
||||
"package": "electron-forge package", |
||||
"make": "electron-forge make", |
||||
"publish": "electron-forge publish", |
||||
"lint": "eslint --ext .ts,.tsx ." |
||||
"main": "build/main.js", |
||||
"license": "MIT", |
||||
"appId": "org.ethereum.remixdesktop", |
||||
"mac": { |
||||
"category": "public.app-category.productivity" |
||||
}, |
||||
"keywords": [], |
||||
"author": { |
||||
"name": "bunsenstraat", |
||||
"email": "filip.mertens@ethereum.org" |
||||
"scripts": { |
||||
"start:dev": "tsc && cross-env NODE_ENV=development electron .", |
||||
"start:production": "tsc && cross-env NODE_ENV=production electron .", |
||||
"pack": "tsc && electron-builder", |
||||
"dist": "tsc && electron-builder --mac --dir", |
||||
"postinstall": "electron-builder install-app-deps" |
||||
}, |
||||
"license": "MIT", |
||||
"devDependencies": { |
||||
"@electron-forge/cli": "^6.1.1", |
||||
"electron": "24.4.0" |
||||
"@electron/rebuild": "^3.2.13", |
||||
"cross-env": "^7.0.3", |
||||
"electron": "^25.0.1", |
||||
"electron-builder": "^23.6.0", |
||||
"typescript": "^5.1.3" |
||||
}, |
||||
"dependencies": { |
||||
"electron-squirrel-startup": "^1.0.0" |
||||
"node-pty": "^0.10.1", |
||||
"electron-is-packaged": "^1.0.2" |
||||
}, |
||||
"build": { |
||||
"productName": "remixdesktop", |
||||
"mac": { |
||||
"target": { |
||||
"target": "default", |
||||
"arch": [ |
||||
"universal" |
||||
] |
||||
}, |
||||
"category": "public.app-category.productivity" |
||||
}, |
||||
"extends": null, |
||||
"files": [ |
||||
"build/**/*" |
||||
] |
||||
} |
||||
} |
||||
|
@ -1,31 +0,0 @@ |
||||
{ |
||||
"name": "remixdesktop", |
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json", |
||||
"sourceRoot": "apps/remixdesktop/src", |
||||
"projectType": "application", |
||||
"targets": { |
||||
"build": { |
||||
"executor": "@nrwl/webpack:webpack", |
||||
"outputs": ["{options.outputPath}"], |
||||
"defaultConfiguration": "development", |
||||
"options": { |
||||
"outputPath": "apps/remixdesktop/.webpack/main", |
||||
"main": "apps/remixdesktop/src/index.ts", |
||||
"tsConfig": "apps/remixdesktop/tsconfig.json", |
||||
"assets": [ |
||||
], |
||||
"webpackConfig": "apps/remixdesktop/webpack.config.js", |
||||
"vendorChunk": false, |
||||
"runtimeChunk": false |
||||
}, |
||||
"configurations": { |
||||
"development": { |
||||
}, |
||||
"production": { |
||||
"optimization": true |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"tags": [] |
||||
} |
@ -1,29 +0,0 @@ |
||||
import { ClientConnector, connectClient, applyApi, Client, PluginClient } from '@remixproject/plugin' |
||||
import type { Message, Api, ApiMap } from '@remixproject/plugin-utils' |
||||
import { IRemixApi } from '@remixproject/plugin-api' |
||||
import { ipcMain } from 'electron' |
||||
import { mainWindow } from '.' |
||||
|
||||
export class ElectronPluginConnector implements ClientConnector { |
||||
|
||||
/** Send a message to the engine */ |
||||
send(message: Partial<Message>) { |
||||
console.log('ElectronPluginConnector send', message) |
||||
mainWindow.webContents.send('fsClient:send', message) |
||||
} |
||||
|
||||
/** Listen to message from the engine */ |
||||
on(cb: (message: Partial<Message>) => void) { |
||||
console.log('ElectronPluginConnector on', cb) |
||||
} |
||||
} |
||||
|
||||
export const createClient = < |
||||
P extends Api, |
||||
App extends ApiMap = Readonly<IRemixApi> |
||||
>(client: PluginClient<P, App> = new PluginClient()): Client<P, App> => { |
||||
const c = client as any |
||||
connectClient(new ElectronPluginConnector(), c) |
||||
applyApi(c) |
||||
return c |
||||
} |
@ -1,14 +1,29 @@ |
||||
import { Engine, PluginManager, Plugin } from '@remixproject/engine'; |
||||
import { ipcMain, ipcRenderer } from 'electron'; |
||||
import { fsPlugin } from './fsPlugin'; |
||||
import { Engine, PluginManager } from '@remixproject/engine'; |
||||
import { 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 plugin = new fsPlugin() |
||||
const fsPlugin = new FSPlugin() |
||||
const gitPlugin = new GitPlugin() |
||||
const xtermPlugin = new XtermPlugin() |
||||
engine.register(appManager) |
||||
engine.register(plugin) |
||||
//appManager.activatePlugin('fs')
|
||||
engine.register(fsPlugin) |
||||
engine.register(gitPlugin) |
||||
engine.register(xtermPlugin) |
||||
|
||||
ipcMain.on('manager:activatePlugin', (event, arg) => { |
||||
appManager.activatePlugin(arg) |
||||
ipcMain.handle('manager:activatePlugin', async (event, arg) => { |
||||
console.log('manager:activatePlugin', arg) |
||||
if(await appManager.isActive(arg)){ |
||||
return true |
||||
} |
||||
return await appManager.activatePlugin(arg) |
||||
}) |
||||
|
||||
app.on('before-quit', async () => { |
||||
await appManager.call('fs', 'closeWatch') |
||||
app.quit() |
||||
}) |
@ -1,70 +1,103 @@ |
||||
import { PluginClient } from "@remixproject/plugin"; |
||||
import { createClient } from "./electronPluginClient" |
||||
import { Engine, PluginManager, Plugin } from '@remixproject/engine'; |
||||
import fs from 'fs' |
||||
import { existsSync } from "fs-extra"; |
||||
import { createElectronClient } from "@remixproject/plugin-electron" |
||||
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 { mainWindow } from "./main"; |
||||
|
||||
const profile = { |
||||
const profile: Profile = { |
||||
displayName: 'fs', |
||||
name: 'fs', |
||||
description: 'fs', |
||||
} |
||||
|
||||
export class fsPlugin extends Plugin { |
||||
client: PluginClient |
||||
export class FSPlugin extends Plugin { |
||||
client: FSPluginClient |
||||
constructor() { |
||||
super(profile) |
||||
this.methods = ['closeWatch'] |
||||
} |
||||
|
||||
onActivation(): void { |
||||
console.log('fsPlugin onActivation') |
||||
this.client = new fsPluginClient() |
||||
this.client = new FSPluginClient() |
||||
} |
||||
|
||||
async closeWatch(): Promise<void> { |
||||
console.log('closeWatch') |
||||
if (this.client) |
||||
await this.client.closeWatch() |
||||
} |
||||
|
||||
} |
||||
|
||||
class fsPluginClient extends PluginClient { |
||||
class FSPluginClient extends PluginClient { |
||||
watcher: chokidar.FSWatcher | undefined |
||||
constructor() { |
||||
super() |
||||
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists'] |
||||
createClient(this) |
||||
this.onload() |
||||
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'exists', 'watch', 'closeWatch', 'currentPath'] |
||||
createElectronClient(this, profile, mainWindow) |
||||
this.onload(() => { |
||||
console.log('fsPluginClient onload') |
||||
}) |
||||
} |
||||
|
||||
async readdir(path: string): Promise<string[]> { |
||||
// call node fs.readdir
|
||||
return fs.readdirSync(path) |
||||
return fs.readdir(path) |
||||
} |
||||
|
||||
async readFile(path: string): Promise<string> { |
||||
return fs.readFileSync(path, 'utf8') |
||||
return fs.readFile(path, 'utf8') |
||||
} |
||||
|
||||
async writeFile(path: string, content: string): Promise<void> { |
||||
return fs.writeFileSync(path, content, 'utf8') |
||||
return fs.writeFile(path, content, 'utf8') |
||||
} |
||||
|
||||
async mkdir(path: string): Promise<void> { |
||||
return fs.mkdirSync(path) |
||||
return fs.mkdir(path) |
||||
} |
||||
|
||||
async rmdir(path: string): Promise<void> { |
||||
return fs.rmdirSync(path) |
||||
return fs.rmdir(path) |
||||
} |
||||
|
||||
async unlink(path: string): Promise<void> { |
||||
return fs.unlinkSync(path) |
||||
return fs.unlink(path) |
||||
} |
||||
|
||||
async rename(oldPath: string, newPath: string): Promise<void> { |
||||
return fs.renameSync(oldPath, newPath) |
||||
return fs.rename(oldPath, newPath) |
||||
} |
||||
|
||||
async stat(path: string): Promise<fs.Stats> { |
||||
return fs.statSync(path) |
||||
async stat(path: string): Promise<Stats> { |
||||
return fs.stat(path) |
||||
} |
||||
|
||||
async exists(path: string): Promise<boolean> { |
||||
return existsSync(path) |
||||
return fs.access(path).then(() => true).catch(() => false) |
||||
} |
||||
|
||||
async currentPath(): Promise<string> { |
||||
return process.cwd() |
||||
} |
||||
|
||||
async watch(path: string): Promise<void> { |
||||
console.log('watch', path) |
||||
if (this.watcher) this.watcher.close() |
||||
this.watcher = |
||||
chokidar.watch(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() |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,53 @@ |
||||
import { Plugin } from "@remixproject/engine"; |
||||
import { PluginClient } from "@remixproject/plugin"; |
||||
import { Profile } from "@remixproject/plugin-utils"; |
||||
import { spawn } from "child_process"; |
||||
import { createElectronClient } from "@remixproject/plugin-electron" |
||||
import { mainWindow } from "./main"; |
||||
|
||||
const profile: Profile = { |
||||
name: 'git', |
||||
displayName: 'Git', |
||||
description: 'Git plugin', |
||||
} |
||||
|
||||
export class GitPlugin extends Plugin { |
||||
client: PluginClient |
||||
constructor() { |
||||
super(profile) |
||||
} |
||||
|
||||
onActivation(): void { |
||||
this.client = new GitPluginClient() |
||||
} |
||||
|
||||
} |
||||
|
||||
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, mainWindow) |
||||
this.onload(() => { |
||||
console.log('GitPluginClient onload') |
||||
}) |
||||
} |
||||
|
||||
async log(path: string): Promise<string> { |
||||
const log = spawn('git', ['log'], { |
||||
cwd: path, |
||||
env: { |
||||
NODE_ENV: 'production', |
||||
PATH: process.env.PATH, |
||||
}, |
||||
}) |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
log.stdout.on('data', (data) => { |
||||
resolve(data.toString()) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
|
||||
} |
@ -1,12 +1,26 @@ |
||||
// electron preload script
|
||||
// write contextbridge to window
|
||||
|
||||
|
||||
import { Message } from '@remixproject/plugin-utils' |
||||
import { contextBridge, ipcRenderer } from 'electron' |
||||
|
||||
console.log('preload.ts') |
||||
|
||||
contextBridge.exposeInMainWorld('api', { |
||||
/* preload script needs statically defined API for each plugin */ |
||||
|
||||
const exposedPLugins = ['fs', 'git', 'xterm'] |
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', { |
||||
activatePlugin: (name: string) => { |
||||
console.log('activatePlugin', name) |
||||
ipcRenderer.send('manager:activatePlugin', name) |
||||
return ipcRenderer.invoke('manager:activatePlugin', name) |
||||
}, |
||||
|
||||
plugins: exposedPLugins.map(name => { |
||||
return { |
||||
name, |
||||
on: (cb:any) => ipcRenderer.on(`${name}:send`, cb), |
||||
send: (message: Partial<Message>) => ipcRenderer.send(`${name}:on`, message) |
||||
} |
||||
}) |
||||
}) |
@ -0,0 +1,74 @@ |
||||
import { Plugin } from "@remixproject/engine"; |
||||
import { PluginClient } from "@remixproject/plugin"; |
||||
import { Profile } from "@remixproject/plugin-utils"; |
||||
import { spawn } from "child_process"; |
||||
import { createElectronClient } from "@remixproject/plugin-electron" |
||||
|
||||
import os from 'os'; |
||||
import * as pty from "node-pty" |
||||
import { mainWindow } from "./main"; |
||||
|
||||
const profile: Profile = { |
||||
name: 'xterm', |
||||
displayName: 'xterm', |
||||
description: 'xterm plugin', |
||||
} |
||||
|
||||
export class XtermPlugin extends Plugin { |
||||
client: PluginClient |
||||
constructor() { |
||||
super(profile) |
||||
} |
||||
|
||||
onActivation(): void { |
||||
this.client = new XtermPluginClient() |
||||
} |
||||
|
||||
} |
||||
|
||||
class XtermPluginClient extends PluginClient { |
||||
terminals: pty.IPty[] = [] |
||||
constructor() { |
||||
super() |
||||
this.methods = ['keystroke', 'createTerminal', 'close'] |
||||
createElectronClient(this, profile, mainWindow) |
||||
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>{ |
||||
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 as { [key: string]: string }, |
||||
}); |
||||
|
||||
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>{ |
||||
this.terminals[pid].kill() |
||||
delete this.terminals[pid] |
||||
this.emit('close', pid) |
||||
} |
||||
|
||||
async sendData(data: string, pid: number){ |
||||
this.emit('data', data, pid) |
||||
} |
||||
|
||||
} |
@ -1,20 +1,17 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"target": "ES6", |
||||
"jsx": "react-jsx", |
||||
"allowJs": true, |
||||
"target": "es6", |
||||
"module": "commonjs", |
||||
"skipLibCheck": true, |
||||
"esModuleInterop": true, |
||||
"noImplicitAny": true, |
||||
"allowSyntheticDefaultImports": true, |
||||
"sourceMap": true, |
||||
"baseUrl": ".", |
||||
"outDir": "dist", |
||||
"moduleResolution": "node", |
||||
"resolveJsonModule": true, |
||||
"paths": { |
||||
"*": ["node_modules/*"] |
||||
"strictPropertyInitialization": false, |
||||
"strict": true, |
||||
"outDir": "build", |
||||
"rootDir": "./src/", |
||||
"noEmitOnError": true, |
||||
"typeRoots": ["node_modules/@types", "./types"] |
||||
} |
||||
}, |
||||
"include": ["src/**/*"] |
||||
} |
@ -1,84 +0,0 @@ |
||||
const { composePlugins, withNx } = require('@nrwl/webpack') |
||||
const webpack = require('webpack') |
||||
const TerserPlugin = require("terser-webpack-plugin") |
||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") |
||||
|
||||
// Nx plugins for webpack.
|
||||
module.exports = composePlugins((input) => { |
||||
|
||||
const config = { |
||||
mode: input.mode === 'production' ? 'production' : 'development', |
||||
} |
||||
|
||||
config.target = 'electron-main' |
||||
config.devtool = 'source-map' |
||||
|
||||
config.output = { |
||||
path: __dirname + '/.webpack/main', |
||||
filename: '[name].js', |
||||
} |
||||
|
||||
config.target = 'electron-preload' |
||||
|
||||
config.entry = { |
||||
index: ['./apps/remixdesktop/src/index.ts'], |
||||
preload: ['./apps/remixdesktop/src/preload.ts'], |
||||
} |
||||
|
||||
|
||||
const mainEntry = config.mode === 'production'? `\`file://$\{require('path').resolve(__dirname, '..', 'renderer', 'index.html')}\``: `'http://localhost:8080'` |
||||
|
||||
config.plugins= [ |
||||
new webpack.DefinePlugin({ |
||||
MAIN_WINDOW_WEBPACK_ENTRY: mainEntry, |
||||
'process.env.MAIN_WINDOW_WEBPACK_ENTRY': mainEntry, |
||||
MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: `\`$\{require('path').resolve(__dirname, 'preload.js')}\``, |
||||
'process.env.MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY': `\`$\{require('path').resolve(__dirname, '..', 'render', 'preload.js')}\``, |
||||
}) |
||||
] |
||||
|
||||
config.module = {} |
||||
config.module.rules = [ |
||||
// Add support for native node modules
|
||||
{ |
||||
// We're specifying native_modules in the test because the asset relocator loader generates a
|
||||
// "fake" .node file which is really a cjs file.
|
||||
test: /native_modules[/\\].+\.node$/, |
||||
use: 'node-loader', |
||||
}, |
||||
/* |
||||
{ |
||||
test: /[/\\]node_modules[/\\].+\.(m?js|node)$/, |
||||
parser: { amd: false }, |
||||
use: { |
||||
loader: '@vercel/webpack-asset-relocator-loader', |
||||
options: { |
||||
outputAssetBase: 'native_modules', |
||||
}, |
||||
}, |
||||
}, |
||||
*/ |
||||
{ |
||||
test: /\.tsx?$/, |
||||
exclude: /(node_modules|\.webpack)/, |
||||
use: { |
||||
loader: 'ts-loader', |
||||
options: { |
||||
transpileOnly: true, |
||||
}, |
||||
}, |
||||
}, |
||||
]; |
||||
config.resolve = {} |
||||
config.resolve.extensions = [ '.js', '.ts', '.jsx', '.tsx', '.css' ] |
||||
config.target = 'electron-preload' |
||||
|
||||
|
||||
config.node = { |
||||
__dirname: false, |
||||
__filename: false, |
||||
} |
||||
|
||||
console.log('config', config) |
||||
return config; |
||||
}); |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue