parent
0483d13e56
commit
1d97df570c
@ -1,16 +0,0 @@ |
||||
{ |
||||
"env": { |
||||
"browser": true, |
||||
"es6": true, |
||||
"node": true |
||||
}, |
||||
"extends": [ |
||||
"eslint:recommended", |
||||
"plugin:@typescript-eslint/eslint-recommended", |
||||
"plugin:@typescript-eslint/recommended", |
||||
"plugin:import/recommended", |
||||
"plugin:import/electron", |
||||
"plugin:import/typescript" |
||||
], |
||||
"parser": "@typescript-eslint/parser" |
||||
} |
@ -1,92 +0,0 @@ |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
lerna-debug.log* |
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
.DS_Store |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
*.lcov |
||||
|
||||
# nyc test coverage |
||||
.nyc_output |
||||
|
||||
# node-waf configuration |
||||
.lock-wscript |
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
build/Release |
||||
|
||||
# Dependency directories |
||||
node_modules/ |
||||
jspm_packages/ |
||||
|
||||
# TypeScript v1 declaration files |
||||
typings/ |
||||
|
||||
# TypeScript cache |
||||
*.tsbuildinfo |
||||
|
||||
# Optional npm cache directory |
||||
.npm |
||||
|
||||
# Optional eslint cache |
||||
.eslintcache |
||||
|
||||
# Optional REPL history |
||||
.node_repl_history |
||||
|
||||
# Output of 'npm pack' |
||||
*.tgz |
||||
|
||||
# Yarn Integrity file |
||||
.yarn-integrity |
||||
|
||||
# dotenv environment variables file |
||||
.env |
||||
.env.test |
||||
|
||||
# parcel-bundler cache (https://parceljs.org/) |
||||
.cache |
||||
|
||||
# next.js build output |
||||
.next |
||||
|
||||
# nuxt.js build output |
||||
.nuxt |
||||
|
||||
# vuepress build output |
||||
.vuepress/dist |
||||
|
||||
# Serverless directories |
||||
.serverless/ |
||||
|
||||
# FuseBox cache |
||||
.fusebox/ |
||||
|
||||
# DynamoDB Local files |
||||
.dynamodb/ |
||||
|
||||
# Webpack |
||||
.webpack/ |
||||
|
||||
# Vite |
||||
.vite/ |
||||
|
||||
# Electron-Forge |
||||
out/ |
@ -1,35 +0,0 @@ |
||||
import type { ForgeConfig } from '@electron-forge/shared-types'; |
||||
import { MakerSquirrel } from '@electron-forge/maker-squirrel'; |
||||
import { MakerZIP } from '@electron-forge/maker-zip'; |
||||
import { MakerDeb } from '@electron-forge/maker-deb'; |
||||
import { MakerRpm } from '@electron-forge/maker-rpm'; |
||||
import { WebpackPlugin } from '@electron-forge/plugin-webpack'; |
||||
|
||||
import { mainConfig } from './webpack.main.config'; |
||||
import { rendererConfig } from './webpack.renderer.config'; |
||||
|
||||
const config: ForgeConfig = { |
||||
packagerConfig: {}, |
||||
rebuildConfig: {}, |
||||
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], |
||||
plugins: [ |
||||
new WebpackPlugin({ |
||||
mainConfig, |
||||
renderer: { |
||||
config: rendererConfig, |
||||
entryPoints: [ |
||||
{ |
||||
html: './src/index.html', |
||||
js: './src/renderer.ts', |
||||
name: 'main_window', |
||||
preload: { |
||||
js: './src/preload.ts', |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
}), |
||||
], |
||||
}; |
||||
|
||||
export default config; |
@ -1,55 +0,0 @@ |
||||
{ |
||||
"name": "1test", |
||||
"productName": "1test", |
||||
"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 ." |
||||
}, |
||||
"keywords": [], |
||||
"author": { |
||||
"name": "filip mertens", |
||||
"email": "filip.mertens@ethereum.org" |
||||
}, |
||||
"license": "MIT", |
||||
"devDependencies": { |
||||
"@electron-forge/cli": "^6.1.1", |
||||
"@electron-forge/maker-deb": "^6.1.1", |
||||
"@electron-forge/maker-rpm": "^6.1.1", |
||||
"@electron-forge/maker-squirrel": "^6.1.1", |
||||
"@electron-forge/maker-zip": "^6.1.1", |
||||
"@electron-forge/plugin-webpack": "^6.1.1", |
||||
"@types/react": "^18.2.8", |
||||
"@types/react-dom": "^18.2.4", |
||||
"@typescript-eslint/eslint-plugin": "^5.0.0", |
||||
"@typescript-eslint/parser": "^5.0.0", |
||||
"@vercel/webpack-asset-relocator-loader": "1.7.3", |
||||
"css-loader": "^6.0.0", |
||||
"electron": "25.0.0", |
||||
"eslint": "^8.0.1", |
||||
"eslint-plugin-import": "^2.25.0", |
||||
"fork-ts-checker-webpack-plugin": "^7.2.13", |
||||
"node-loader": "^2.0.0", |
||||
"style-loader": "^3.0.0", |
||||
"ts-loader": "^9.2.2", |
||||
"ts-node": "^10.0.0", |
||||
"typescript": "~4.5.4" |
||||
}, |
||||
"dependencies": { |
||||
"chokidar": "^3.5.3", |
||||
"electron-squirrel-startup": "^1.0.0", |
||||
"fix-path": "^4.0.0", |
||||
"isomorphic-git": "^1.24.0", |
||||
"node-pty": "0.10.1", |
||||
"react": "^18.2.0", |
||||
"react-dom": "^18.2.0", |
||||
"xterm": "^5.1.0", |
||||
"xterm-addon-fit": "^0.7.0", |
||||
"xterm-for-react": "^1.0.4" |
||||
} |
||||
} |
@ -1,14 +0,0 @@ |
||||
import * as ReactDOM from 'react-dom'; |
||||
import { RemixUiXterminals } from './remix/ui/remix-ui-xterminals'; |
||||
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} /> |
||||
<RemixUIFileDialog plugin={filePanel} /> |
||||
</> |
||||
) |
@ -1,39 +0,0 @@ |
||||
import { Engine, PluginManager } from '@remixproject/engine'; |
||||
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)
|
||||
|
||||
appManager.activatePlugin('fs') |
||||
appManager.activatePlugin('git') |
||||
appManager.activatePlugin('xterm') |
||||
|
||||
ipcMain.handle('manager:activatePlugin', async (event, plugin) => { |
||||
console.log('manager:activatePlugin', plugin, event.sender.id) |
||||
return await 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,134 +0,0 @@ |
||||
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' |
||||
} |
||||
|
||||
export class FSPlugin extends ElectronBasePlugin { |
||||
clients: FSPluginClient[] = [] |
||||
constructor() { |
||||
super(profile, clientProfile, FSPluginClient) |
||||
this.methods = [...super.methods, 'closeWatch'] |
||||
} |
||||
|
||||
async closeWatch(): Promise<void> { |
||||
for (const client of this.clients) { |
||||
await client.closeWatch() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
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 |
||||
workingDir: string = '/Volumes/bunsen/code/empty/' |
||||
|
||||
constructor(webContentsId: number, profile: Profile) { |
||||
super(webContentsId, profile) |
||||
this.onload(() => { |
||||
console.log('fsPluginClient onload') |
||||
}) |
||||
} |
||||
|
||||
async readdir(path: string): Promise<string[]> { |
||||
// call node fs.readdir
|
||||
return fs.readdir(this.fixPath(path)) |
||||
} |
||||
|
||||
async readFile(path: string): Promise<string> { |
||||
return fs.readFile(this.fixPath(path), 'utf8') |
||||
} |
||||
|
||||
async writeFile(path: string, content: string): Promise<void> { |
||||
return fs.writeFile(this.fixPath(path), content, 'utf8') |
||||
} |
||||
|
||||
async mkdir(path: string): Promise<void> { |
||||
return fs.mkdir(this.fixPath(path)) |
||||
} |
||||
|
||||
async rmdir(path: string): Promise<void> { |
||||
return fs.rmdir(this.fixPath(path)) |
||||
} |
||||
|
||||
async unlink(path: string): Promise<void> { |
||||
return fs.unlink(this.fixPath(path)) |
||||
} |
||||
|
||||
async rename(oldPath: string, newPath: string): Promise<void> { |
||||
return fs.rename(this.fixPath(oldPath), this.fixPath(newPath)) |
||||
} |
||||
|
||||
async stat(path: string): Promise<any> { |
||||
const stat = await fs.stat(path) |
||||
//console.log('stat', path, stat)
|
||||
const isDirectory = stat.isDirectory() |
||||
return { |
||||
...stat, |
||||
isDirectoryValue: isDirectory |
||||
} |
||||
} |
||||
|
||||
async lstat(path: string): Promise<any> { |
||||
const lstat = await fs.lstat(this.fixPath(path)) |
||||
return lstat |
||||
} |
||||
|
||||
|
||||
async exists(path: string): Promise<boolean> { |
||||
return fs.access(this.fixPath(path)).then(() => true).catch(() => false) |
||||
} |
||||
|
||||
async currentPath(): Promise<string> { |
||||
return process.cwd() |
||||
} |
||||
|
||||
async watch(path: string): Promise<void> { |
||||
if (this.watcher) this.watcher.close() |
||||
this.watcher = |
||||
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('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,53 +0,0 @@ |
||||
import { PluginClient } from "@remixproject/plugin"; |
||||
import { Profile } from "@remixproject/plugin-utils"; |
||||
import { spawn } from "child_process"; |
||||
import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin"; |
||||
|
||||
const profile: Profile = { |
||||
name: 'git', |
||||
displayName: 'Git', |
||||
description: 'Git plugin', |
||||
} |
||||
|
||||
export class GitPlugin extends ElectronBasePlugin { |
||||
client: PluginClient |
||||
constructor() { |
||||
super(profile, clientProfile, GitPluginClient) |
||||
} |
||||
|
||||
} |
||||
|
||||
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, profile: Profile) { |
||||
super(webContentsId, profile) |
||||
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,54 +0,0 @@ |
||||
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<boolean>; |
||||
closeClient(windowId: number): Promise<boolean>; |
||||
} |
||||
|
||||
export abstract class ElectronBasePlugin extends Plugin implements ElectronBasePluginInterface { |
||||
clients: ElectronBasePluginClient[] = []; |
||||
clientClass: any |
||||
clientProfile: Profile |
||||
constructor(profile: Profile, clientProfile: Profile, clientClass: any) { |
||||
super(profile); |
||||
this.methods = ['createClient', 'closeClient']; |
||||
this.clientClass = clientClass; |
||||
this.clientProfile = clientProfile; |
||||
} |
||||
|
||||
async createClient(webContentsId: number): Promise<boolean> { |
||||
if (this.clients.find(client => client.webContentsId === webContentsId)) return true |
||||
const client = new this.clientClass(webContentsId, this.clientProfile); |
||||
this.clients.push(client); |
||||
return new Promise((resolve, reject) => { |
||||
client.onload(() => { |
||||
resolve(true) |
||||
}) |
||||
}) |
||||
} |
||||
async closeClient(windowId: number): Promise<boolean> { |
||||
this.clients = this.clients.filter(client => client.webContentsId !== windowId) |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
export class ElectronBasePluginClient extends PluginClient { |
||||
window: Electron.BrowserWindow; |
||||
webContentsId: number; |
||||
constructor(webcontentsid: number, profile: Profile, methods: string[] = []) { |
||||
super(); |
||||
this.methods = profile.methods; |
||||
this.webContentsId = webcontentsid; |
||||
BrowserWindow.getAllWindows().forEach((window) => { |
||||
if (window.webContents.id === webcontentsid) { |
||||
this.window = window; |
||||
} |
||||
}); |
||||
createElectronClient(this, profile, this.window); |
||||
} |
||||
} |
||||
|
@ -1,34 +0,0 @@ |
||||
import { ClientConnector, connectClient, applyApi, Client, PluginClient } from '@remixproject/plugin' |
||||
import type { Message, Api, ApiMap, Profile } from '@remixproject/plugin-utils' |
||||
import { IRemixApi } from '@remixproject/plugin-api' |
||||
import { ipcMain } from 'electron' |
||||
|
||||
export class ElectronPluginClientConnector implements ClientConnector { |
||||
|
||||
constructor(public profile: Profile, public browserWindow: Electron.BrowserWindow) {
|
||||
} |
||||
|
||||
/** Send a message to the engine */ |
||||
send(message: Partial<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:' + this.browserWindow.webContents.id, (event, message) => { |
||||
cb(message) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
export const createElectronClient = < |
||||
P extends Api, |
||||
App extends ApiMap = Readonly<IRemixApi> |
||||
>(client: PluginClient<P, App> = new PluginClient(), profile: Profile |
||||
, window: Electron.BrowserWindow |
||||
): Client<P, App> => { |
||||
const c = client as any |
||||
connectClient(new ElectronPluginClientConnector(profile, window), c) |
||||
applyApi(c) |
||||
return c |
||||
} |
@ -1,18 +0,0 @@ |
||||
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); |
||||
} |
||||
}; |
@ -1,36 +0,0 @@ |
||||
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'] |
||||
} |
||||
] |
||||
}; |
||||
}; |
@ -1,75 +0,0 @@ |
||||
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 os from 'os'; |
||||
import * as pty from "node-pty" |
||||
import { ElectronBasePlugin, ElectronBasePluginClient } from "./lib/electronBasePlugin"; |
||||
|
||||
const profile: Profile = { |
||||
name: 'xterm', |
||||
displayName: 'xterm', |
||||
description: 'xterm plugin', |
||||
} |
||||
|
||||
export class XtermPlugin extends ElectronBasePlugin { |
||||
client: PluginClient |
||||
constructor() { |
||||
super(profile, clientProfile, XtermPluginClient) |
||||
} |
||||
|
||||
} |
||||
|
||||
const clientProfile: Profile = { |
||||
name: 'xterm', |
||||
displayName: 'xterm', |
||||
description: 'xterm plugin', |
||||
methods: ['createTerminal', 'close', 'keystroke'] |
||||
} |
||||
|
||||
class XtermPluginClient extends ElectronBasePluginClient { |
||||
|
||||
terminals: pty.IPty[] = [] |
||||
constructor(webContentsId: number, profile: Profile) { |
||||
super(webContentsId, profile) |
||||
this.onload(() => { |
||||
console.log('XtermPluginClient onload') |
||||
}) |
||||
} |
||||
|
||||
async keystroke(key: string, pid: number): Promise<void> { |
||||
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 |
||||
}); |
||||
|
||||
ptyProcess.onData((data: string) => { |
||||
this.sendData(data, ptyProcess.pid); |
||||
}) |
||||
this.terminals[ptyProcess.pid] = ptyProcess |
||||
|
||||
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,15 +0,0 @@ |
||||
export interface IElectronAPI { |
||||
activatePlugin: (name: string) => Promise<boolean> |
||||
plugins: { |
||||
name: string |
||||
on: (cb: any) => void |
||||
send: (message: Partial<Message>) => void |
||||
}[] |
||||
getWindowId: () => string | undefined |
||||
} |
||||
|
||||
declare global { |
||||
interface Window { |
||||
electronAPI: IElectronAPI |
||||
} |
||||
} |
@ -1,7 +0,0 @@ |
||||
body { |
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, |
||||
Arial, sans-serif; |
||||
margin: auto; |
||||
max-width: 38rem; |
||||
padding: 2rem; |
||||
} |
@ -1,11 +0,0 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<title>Hello Terminal!</title> |
||||
|
||||
</head> |
||||
<body> |
||||
<div id="app"></div> |
||||
</body> |
||||
</html> |
@ -1,302 +0,0 @@ |
||||
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).
|
||||
declare const MAIN_WINDOW_WEBPACK_ENTRY: string; |
||||
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string; |
||||
|
||||
fixPath(); |
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require('electron-squirrel-startup')) { |
||||
app.quit(); |
||||
} |
||||
export let mainWindow: BrowserWindow; |
||||
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, |
||||
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') |
||||
}; |
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// 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.
|
||||
app.on('window-all-closed', () => { |
||||
if (process.platform !== 'darwin') { |
||||
app.quit(); |
||||
} |
||||
}); |
||||
|
||||
app.on('activate', () => { |
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) { |
||||
createWindow(); |
||||
} |
||||
}); |
||||
|
||||
// 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
|
||||
|
||||
|
||||
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)) |
||||
|
||||
import fs from 'fs/promises' |
||||
import { readlink, stat } from 'fs'; |
||||
//const menu = Menu.buildFromTemplate(shellMenu([], undefined))
|
||||
//Menu.setApplicationMenu(menu)
|
||||
|
||||
const myFS = { |
||||
promises: { |
||||
readdir: async (path: string, options: any): Promise<string[]> => { |
||||
// call node fs.readdir
|
||||
//console.log('myFS.readdir', path, options)
|
||||
const file = await fs.readdir(path, { |
||||
encoding: 'utf8', |
||||
}) |
||||
//console.log('myFS.readdir', file)
|
||||
return file |
||||
}, |
||||
|
||||
readFile: async (path: string, options: any): Promise<string> => { |
||||
//console.log('myFS.readFile', path, options)
|
||||
const file = await (fs as any).readFile(path, options) |
||||
//console.log('myFS.readFile', file)
|
||||
return file |
||||
|
||||
|
||||
}, |
||||
|
||||
async writeFile(path: string, content: string): Promise<void> { |
||||
return fs.writeFile(path, content, 'utf8') |
||||
}, |
||||
|
||||
async mkdir(path: string): Promise<void> { |
||||
return fs.mkdir(path) |
||||
}, |
||||
|
||||
async rmdir(path: string): Promise<void> { |
||||
return fs.rmdir(path) |
||||
}, |
||||
|
||||
async unlink(path: string): Promise<void> { |
||||
return fs.unlink(path) |
||||
}, |
||||
|
||||
async rename(oldPath: string, newPath: string): Promise<void> { |
||||
return fs.rename(oldPath, newPath) |
||||
}, |
||||
|
||||
async stat(path: string): Promise<any> { |
||||
//console.log('myFS.stat', path)
|
||||
const stat = await fs.stat(path) |
||||
//console.log('myFS.stat', stat)
|
||||
return stat |
||||
}, |
||||
|
||||
async lstat(path: string): Promise<any> { |
||||
const lstat = await fs.lstat(path) |
||||
//console.log('myFS.stat', path, lstat)
|
||||
return lstat |
||||
}, |
||||
|
||||
readlink: async (path: string): Promise<string> => { |
||||
return fs.readlink(path) |
||||
}, |
||||
symlink: async (target: string, path: string): Promise<void> => { |
||||
return fs.symlink(target, path) |
||||
} |
||||
|
||||
|
||||
} |
||||
} |
||||
|
||||
console.log('myFS', myFS) |
||||
import git, { CommitObject, ReadCommitResult } from 'isomorphic-git' |
||||
async function checkGit() { |
||||
|
||||
|
||||
const files = await git.statusMatrix({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', filepaths: ['apps/1test/src/index.ts'] }); |
||||
console.log('GIT', files) |
||||
} |
||||
|
||||
|
||||
//checkGit()
|
||||
|
||||
/* |
||||
setInterval(() => { |
||||
|
||||
const startTime = Date.now() |
||||
checkGit() |
||||
.then(() => { |
||||
console.log('checkGit', Date.now() - startTime) |
||||
}) |
||||
|
||||
}, 3000) |
||||
|
||||
*/ |
||||
/* |
||||
git.add({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', filepath: 'test.txt' }).then(() => { |
||||
console.log('git add') |
||||
}).catch((e: any) => { |
||||
console.log('git add error', e) |
||||
}) |
||||
|
||||
git.log({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', depth:10 }).then((log: any) => { |
||||
console.log('git log', log) |
||||
}) |
||||
*/ |
||||
|
||||
// run a shell command
|
||||
import { exec } from 'child_process'; |
||||
import { promisify } from 'util'; |
||||
const execAsync = promisify(exec); |
||||
|
||||
const statusTransFormMatrix = (status: string) => { |
||||
switch (status) { |
||||
case '??': |
||||
return [0, 2, 0] |
||||
case 'A ': |
||||
return [0, 2, 2] |
||||
case 'M ': |
||||
return [1, 2, 2] |
||||
case 'MM': |
||||
return [1, 2, 3] |
||||
case ' M': |
||||
return [1, 0, 1] |
||||
case ' D': |
||||
return [0, 2, 0] |
||||
case 'D ': |
||||
return [1, 0, 0] |
||||
case 'AM': |
||||
return [0, 2, 3] |
||||
default: |
||||
return [-1, -1, -1] |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
execAsync('git status --porcelain -uall', { cwd: '/Volumes/bunsen/code/rmproject2/remix-project' }).then(async (result: any) => { |
||||
//console.log('git status --porcelain -uall', result.stdout)
|
||||
// parse the result.stdout
|
||||
const lines = result.stdout.split('\n') |
||||
const files: any = [] |
||||
const fileNames: any = [] |
||||
//console.log('lines', lines)
|
||||
lines.forEach((line: string) => { |
||||
// get the first two characters of the line
|
||||
const status = line.slice(0, 2) |
||||
|
||||
const file = line.split(' ').pop() |
||||
|
||||
//console.log('line', line)
|
||||
if (status && file) { |
||||
fileNames.push(file) |
||||
files.push([ |
||||
file, |
||||
...statusTransFormMatrix(status) |
||||
]) |
||||
} |
||||
} |
||||
) |
||||
// sort files by first column
|
||||
files.sort((a: any, b: any) => { |
||||
if (a[0] < b[0]) { |
||||
return -1 |
||||
} |
||||
if (a[0] > b[0]) { |
||||
return 1 |
||||
} |
||||
return 0 |
||||
}) |
||||
|
||||
//console.log('files', files, files.length)
|
||||
const iso = await git.statusMatrix({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', filepaths: fileNames }); |
||||
//console.log('GIT', iso, iso.length)
|
||||
}) |
||||
|
||||
git.log({ fs: myFS, dir: '/Volumes/bunsen/code/rmproject2/remix-project', depth:3 }).then((log: ReadCommitResult[]) => { |
||||
log.forEach((commit: ReadCommitResult) => { |
||||
console.log('commit', commit.commit.parent) |
||||
}) |
||||
}) |
||||
|
||||
// exec git log --pretty=format:"%h - %an, %ar : %s" -n 10
|
||||
execAsync(`git log --pretty=format:'{ "oid":"%H", "message":"%s", "author":"%an", "email": "%ae", "timestamp":"%at", "tree": "%T", "committer": "%cn", "committer-email": "%ce", "committer-timestamp": "%ct", "parent": "%P" }' -n 3`, { cwd: '/Volumes/bunsen/code/rmproject2/remix-project' }).then(async (result: any) =>{ |
||||
//console.log('git log', result.stdout)
|
||||
const lines = result.stdout.split('\n') |
||||
const commits: ReadCommitResult[] = [] |
||||
lines.forEach((line: string) => { |
||||
console.log('line', line) |
||||
const data = JSON.parse(line) |
||||
let commit:ReadCommitResult = {} as ReadCommitResult |
||||
commit.oid = data.oid |
||||
commit.commit = {} as CommitObject |
||||
commit.commit.message = data.message |
||||
commit.commit.tree = data.tree |
||||
commit.commit.committer = {} as any |
||||
commit.commit.committer.name = data.committer |
||||
commit.commit.committer.email = data['committer-email'] |
||||
commit.commit.committer.timestamp = data['committer-timestamp'] |
||||
commit.commit.author = {} as any |
||||
commit.commit.author.name = data.author |
||||
commit.commit.author.email = data.email |
||||
commit.commit.author.timestamp = data.timestamp |
||||
commit.commit.parent = [data.parent] |
||||
console.log('commit', commit) |
||||
commits.push(commit) |
||||
}) |
||||
}) |
@ -1,43 +0,0 @@ |
||||
|
||||
import { Message } from '@remixproject/plugin-utils' |
||||
import { contextBridge, ipcRenderer } from 'electron' |
||||
|
||||
console.log('preload.ts') |
||||
|
||||
/* preload script needs statically defined API for each plugin */ |
||||
|
||||
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>) => { |
||||
console.log('send', message, `${name}:on:${webContentsId}`) |
||||
ipcRenderer.send(`${name}:on:${webContentsId}`, message) |
||||
} |
||||
} |
||||
}) |
||||
}) |
@ -1,97 +0,0 @@ |
||||
import { ElectronPlugin } from './lib/electronPlugin'; |
||||
|
||||
let workingDir = '/Volumes/bunsen/code/empty/' |
||||
|
||||
const fixPath = (path: string) => { |
||||
/* |
||||
// if it starts with /, it's an absolute path remove it
|
||||
if (path.startsWith('/')) { |
||||
path = path.slice(1) |
||||
} |
||||
|
||||
path = workingDir + path |
||||
*/ |
||||
|
||||
|
||||
return path |
||||
} |
||||
|
||||
export class fsPlugin extends ElectronPlugin { |
||||
public fs: any |
||||
|
||||
constructor() { |
||||
super({ |
||||
displayName: 'fs', |
||||
name: 'fs', |
||||
description: 'fs', |
||||
}) |
||||
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 |
||||
}) |
||||
} |
||||
|
||||
} |
@ -1,14 +0,0 @@ |
||||
import { ElectronPlugin } from './lib/electronPlugin'; |
||||
|
||||
export class gitPlugin extends ElectronPlugin { |
||||
constructor(){ |
||||
super({ |
||||
displayName: 'git', |
||||
name: 'git', |
||||
description: 'git', |
||||
}) |
||||
this.methods = ['log', 'status', 'add', 'commit', 'push', 'pull', 'clone', 'checkout', 'branch', 'merge', 'reset', 'revert', 'diff', 'stash', 'apply', 'cherryPick', 'rebase', 'tag', 'fetch', 'remote', 'config', 'show', 'init', 'help', 'version'] |
||||
} |
||||
|
||||
|
||||
} |
@ -1,176 +0,0 @@ |
||||
import type { Profile, Message } from '@remixproject/plugin-utils' |
||||
import { Plugin } from '@remixproject/engine'; |
||||
|
||||
export abstract class ElectronPlugin extends Plugin { |
||||
protected loaded: boolean |
||||
protected id = 0 |
||||
protected pendingRequest: Record<number, (result: any, error: Error | string) => void> = {} |
||||
protected api: { |
||||
send: (message: Partial<Message>) => void |
||||
on: (cb: (event: any, message: any) => void) => void |
||||
} |
||||
|
||||
profile: Profile |
||||
constructor(profile: Profile) { |
||||
super(profile) |
||||
this.loaded = false |
||||
|
||||
if(!window.electronAPI) throw new Error('ElectronPluginConnector requires window.api') |
||||
if(!window.electronAPI.plugins) throw new Error('ElectronPluginConnector requires window.api.plugins') |
||||
|
||||
window.electronAPI.plugins.find((plugin: any) => { |
||||
if(plugin.name === profile.name){ |
||||
this.api = plugin |
||||
return true |
||||
} |
||||
}) |
||||
|
||||
if(!this.api) throw new Error(`ElectronPluginConnector requires window.api.plugins.${profile.name} to be defined in preload.ts`) |
||||
|
||||
this.api.on((event: any, message: any) => { |
||||
this.getMessage(message) |
||||
}) |
||||
|
||||
|
||||
} |
||||
|
||||
/** |
||||
* Send a message to the external plugin |
||||
* @param message the message passed to the plugin |
||||
*/ |
||||
protected send(message: Partial<Message>): void { |
||||
if(this.loaded) |
||||
this.api.send(message) |
||||
} |
||||
/** |
||||
* Open connection with the plugin |
||||
* @param name The name of the plugin should connect to |
||||
*/ |
||||
protected async connect(name: string) { |
||||
const connected = await window.electronAPI.activatePlugin(name) |
||||
if(connected && !this.loaded){ |
||||
this.handshake() |
||||
} |
||||
|
||||
} |
||||
/** Close connection with the plugin */ |
||||
protected disconnect(): any | Promise<any> { |
||||
// TODO: Disconnect from the plugin
|
||||
} |
||||
|
||||
async activate() { |
||||
await this.connect(this.profile.name) |
||||
return super.activate() |
||||
} |
||||
|
||||
async deactivate() { |
||||
this.loaded = false |
||||
await this.disconnect() |
||||
return super.deactivate() |
||||
} |
||||
|
||||
/** Call a method from this plugin */ |
||||
protected callPluginMethod(key: string, payload: any[] = []): Promise<any> { |
||||
const action = 'request' |
||||
const id = this.id++ |
||||
const requestInfo = this.currentRequest |
||||
const name = this.name |
||||
const promise = new Promise((res, rej) => { |
||||
this.pendingRequest[id] = (result: any[], error: Error | string) => error ? rej (error) : res(result) |
||||
}) |
||||
this.send({ id, action, key, payload, requestInfo, name }) |
||||
return promise |
||||
} |
||||
|
||||
/** Perform handshake with the client if not loaded yet */ |
||||
protected async handshake() { |
||||
if (!this.loaded) { |
||||
this.loaded = true |
||||
let methods: string[]; |
||||
try { |
||||
methods = await this.callPluginMethod('handshake', [this.profile.name]) |
||||
} catch (err) { |
||||
this.loaded = false |
||||
throw err; |
||||
} |
||||
this.emit('loaded', this.name) |
||||
if (methods) { |
||||
this.profile.methods = methods |
||||
this.call('manager', 'updateProfile', this.profile) |
||||
} |
||||
} else { |
||||
// If there is a broken connection we want send back the handshake to the plugin client
|
||||
return this.callPluginMethod('handshake', [this.profile.name]) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* React when a message comes from client |
||||
* @param message The message sent by the client |
||||
*/ |
||||
protected async getMessage(message: Message) { |
||||
// Check for handshake request from the client
|
||||
if (message.action === 'request' && message.key === 'handshake') { |
||||
console.log('ElectronPluginConnector getMessage handshake', message) |
||||
return this.handshake() |
||||
} |
||||
|
||||
switch (message.action) { |
||||
// Start listening on an event
|
||||
case 'on': |
||||
case 'listen': { |
||||
const { name, key } = message |
||||
const action = 'notification' |
||||
this.on(name, key, (...payload: any[]) => this.send({ action, name, key, payload })) |
||||
break |
||||
} |
||||
case 'off': { |
||||
const { name, key } = message |
||||
this.off(name, key) |
||||
break |
||||
} |
||||
case 'once': { |
||||
const { name, key } = message |
||||
const action = 'notification' |
||||
this.once(name, key, (...payload: any) => this.send({ action, name, key, payload })) |
||||
break |
||||
} |
||||
// Emit an event
|
||||
case 'emit': |
||||
case 'notification': { |
||||
if (!message.payload) break |
||||
this.emit(message.key, ...message.payload) |
||||
break |
||||
} |
||||
// Call a method
|
||||
case 'call': |
||||
case 'request': { |
||||
const action = 'response' |
||||
try { |
||||
const payload = await this.call(message.name, message.key, ...message.payload) |
||||
const error: any = undefined |
||||
this.send({ ...message, action, payload, error }) |
||||
} catch (err) { |
||||
const payload: any = undefined |
||||
const error = err.message || err |
||||
this.send({ ...message, action, payload, error }) |
||||
} |
||||
break |
||||
} |
||||
case 'cancel': { |
||||
const payload = this.cancel(message.name, message.key) |
||||
break; |
||||
} |
||||
// Return result from exposed method
|
||||
case 'response': { |
||||
const { id, payload, error } = message |
||||
this.pendingRequest[id](payload, error) |
||||
delete this.pendingRequest[id] |
||||
break |
||||
} |
||||
default: { |
||||
throw new Error('Message should be a notification, request or response') |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,40 +0,0 @@ |
||||
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>)} |
||||
</> |
||||
) |
||||
} |
@ -1,44 +0,0 @@ |
||||
import React, { useState, useEffect, forwardRef } from 'react' // eslint-disable-line
|
||||
import { ElectronPlugin } from '../lib/electronPlugin' |
||||
import { XTerm } from 'xterm-for-react' |
||||
|
||||
|
||||
export interface RemixUiXtermProps { |
||||
plugin: ElectronPlugin |
||||
pid: number |
||||
send: (data: string, pid: number) => void |
||||
timeStamp: number |
||||
setTerminalRef: (pid: number, ref: any) => void |
||||
} |
||||
|
||||
const RemixUiXterm = (props: RemixUiXtermProps) => { |
||||
const { plugin, pid, send, timeStamp } = props |
||||
const xtermRef = React.useRef(null) |
||||
|
||||
useEffect(() => { |
||||
console.log('remix-ui-xterm ref', xtermRef.current) |
||||
props.setTerminalRef(pid, xtermRef.current) |
||||
}, [xtermRef.current]) |
||||
|
||||
const onKey = (event: { key: string; domEvent: KeyboardEvent }) => { |
||||
send(event.key, pid) |
||||
} |
||||
|
||||
const onData = (data: string) => { |
||||
console.log('onData', data) |
||||
} |
||||
|
||||
const closeTerminal = () => { |
||||
plugin.call('xterm', 'close', pid) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<XTerm ref={xtermRef} onData={onData} onKey={onKey}></XTerm> |
||||
<button onClick={closeTerminal}>close</button> |
||||
</> |
||||
) |
||||
|
||||
} |
||||
|
||||
export default RemixUiXterm |
@ -1,95 +0,0 @@ |
||||
import React, { useState, useEffect } from 'react' // eslint-disable-line
|
||||
import { ElectronPlugin } from '../lib/electronPlugin' |
||||
import RemixUiXterm from './remix-ui-xterm' |
||||
|
||||
export interface RemixUiXterminalsProps { |
||||
plugin: ElectronPlugin |
||||
} |
||||
|
||||
export interface xtermState { |
||||
pid: number |
||||
queue: string |
||||
timeStamp: number |
||||
ref: any |
||||
} |
||||
|
||||
export const RemixUiXterminals = (props: RemixUiXterminalsProps) => { |
||||
const [terminals, setTerminals] = useState<xtermState[]>([]) |
||||
const [workingDir, setWorkingDir] = useState<string>('') |
||||
const { plugin } = props |
||||
|
||||
useEffect(() => { |
||||
plugin.on('xterm', 'loaded', async () => { |
||||
}) |
||||
plugin.on('xterm', 'data', async (data: string, pid: number) => { |
||||
writeToTerminal(data, pid) |
||||
}) |
||||
|
||||
plugin.on('xterm', 'close', async (pid: number) => { |
||||
setTerminals(prevState => { |
||||
return prevState.filter(xtermState => xtermState.pid !== pid) |
||||
}) |
||||
}) |
||||
|
||||
plugin.on('fs', 'workingDirChanged', (path: string) => { |
||||
setWorkingDir(path) |
||||
}) |
||||
}, []) |
||||
|
||||
const writeToTerminal = (data: string, pid: number) => { |
||||
setTerminals(prevState => { |
||||
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] |
||||
}) |
||||
} |
||||
|
||||
const send = (data: string, pid: number) => { |
||||
plugin.call('xterm', 'keystroke', data, pid) |
||||
} |
||||
|
||||
const createTerminal = async () => { |
||||
const pid = await plugin.call('xterm', 'createTerminal', workingDir) |
||||
|
||||
setTerminals(prevState => { |
||||
return [...prevState, { |
||||
pid: pid, |
||||
queue: '', |
||||
timeStamp: Date.now(), |
||||
ref: null |
||||
}] |
||||
}) |
||||
} |
||||
|
||||
const setTerminalRef = (pid: number, ref: any) => { |
||||
setTerminals(prevState => { |
||||
const terminal = prevState.find(xtermState => xtermState.pid === pid) |
||||
terminal.ref = ref |
||||
if(terminal.queue) { |
||||
ref.terminal.write(terminal.queue) |
||||
terminal.queue = '' |
||||
} |
||||
return [...prevState] |
||||
}) |
||||
} |
||||
|
||||
|
||||
return (<> |
||||
<button onClick={() => { |
||||
createTerminal() |
||||
}}>create terminal</button> |
||||
|
||||
{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} plugin={plugin}></RemixUiXterm> |
||||
</div> |
||||
) |
||||
})} |
||||
</>) |
||||
} |
||||
|
@ -1,11 +0,0 @@ |
||||
import { ElectronPlugin } from './lib/electronPlugin'; |
||||
|
||||
export class xtermPlugin extends ElectronPlugin { |
||||
constructor(){ |
||||
super({ |
||||
displayName: 'xterm', |
||||
name: 'xterm', |
||||
description: 'xterm', |
||||
}) |
||||
} |
||||
} |
@ -1,111 +0,0 @@ |
||||
|
||||
import { Engine, Plugin, PluginManager } from '@remixproject/engine'; |
||||
import { fsPlugin } from './remix/fsPlugin'; |
||||
import { gitPlugin } from './remix/gitPlugin'; |
||||
import { Terminal } from 'xterm'; |
||||
import 'xterm/css/xterm.css'; |
||||
import { FitAddon } from 'xterm-addon-fit'; |
||||
import { xtermPlugin } from './remix/xtermPlugin'; |
||||
|
||||
class MyAppManager extends PluginManager { |
||||
onActivation(): void { |
||||
this.on('fs', 'loaded', async () => { |
||||
console.log('fs loaded') |
||||
const files = await this.call('fs', 'readdir', './') |
||||
console.log('files', files) |
||||
}) |
||||
/* |
||||
this.on('fs', 'loaded', async () => { |
||||
const files = await this.call('fs', 'readdir', './') |
||||
console.log('files', files) |
||||
let exists = await this.call('fs', 'exists', '/Volumes/bunsen/code/rmproject2/remix-project/') |
||||
console.log('exists', exists) |
||||
exists = await this.call('fs', 'exists', './notexists') |
||||
console.log('exists', exists) |
||||
// stat test
|
||||
const stat = await this.call('fs', 'stat', '/Volumes/bunsen/code/rmproject2/remix-project/') |
||||
console.log('stat', stat) |
||||
// read file test
|
||||
const content = await this.call('fs', 'readFile', './src/index.html') |
||||
console.log('content', content) |
||||
|
||||
await this.call('fs', 'watch', '/Volumes/bunsen/code/rmproject2/remix-project/') |
||||
|
||||
this.on('fs', 'change', (path: string, stats: any) => { |
||||
console.log('change', path, stats) |
||||
}) |
||||
}) |
||||
|
||||
this.on('git', 'loaded', async () => { |
||||
//const log = await this.call('git', 'log', '/Volumes/bunsen/code/rmproject2/remix-project/')
|
||||
//console.log('log', log)
|
||||
}) |
||||
*/ |
||||
this.on('xterm', 'loaded', async () => { |
||||
console.log('xterm loaded') |
||||
/* |
||||
const term = new Terminal(); |
||||
const fitAddon = new FitAddon(); |
||||
term.loadAddon(fitAddon); |
||||
term.open(document.getElementById('terminal')); |
||||
fitAddon.fit(); |
||||
|
||||
|
||||
const pid = await this.call('xterm', 'createTerminal') |
||||
console.log('pid', pid) |
||||
this.on('xterm', 'data', (data: string, pid: number) => { |
||||
console.log('data', data) |
||||
term.write(data) |
||||
} |
||||
) |
||||
|
||||
term.onData((data) => { |
||||
console.log('term.onData', data) |
||||
this.call('xterm', 'keystroke', data, pid) |
||||
} |
||||
); |
||||
*/ |
||||
}) |
||||
} |
||||
} |
||||
|
||||
|
||||
const engine = new Engine() |
||||
const appManager = new MyAppManager() |
||||
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 () => { |
||||
const files = await appManager.call('fs', 'readdir', './src') |
||||
console.log('files', files) |
||||
}, 1000) |
||||
|
||||
|
||||
|
||||
import './app' |
||||
import { ElectronPlugin } from './remix/lib/electronPlugin'; |
||||
|
@ -1,20 +0,0 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"target": "ES6", |
||||
"allowJs": true, |
||||
"module": "commonjs", |
||||
"jsx": "react-jsx", |
||||
"skipLibCheck": true, |
||||
"esModuleInterop": true, |
||||
"noImplicitAny": true, |
||||
"sourceMap": true, |
||||
"baseUrl": ".", |
||||
"outDir": "dist", |
||||
"moduleResolution": "node", |
||||
"resolveJsonModule": true, |
||||
"paths": { |
||||
"*": ["node_modules/*"] |
||||
} |
||||
}, |
||||
"include": ["src/**/*"] |
||||
} |
@ -1,18 +0,0 @@ |
||||
import type { Configuration } from 'webpack'; |
||||
|
||||
import { rules } from './webpack.rules'; |
||||
|
||||
export const mainConfig: Configuration = { |
||||
/** |
||||
* This is the main entry point for your application, it's the first file |
||||
* that runs in the main process. |
||||
*/ |
||||
entry: './src/index.ts', |
||||
// Put your normal webpack config below here
|
||||
module: { |
||||
rules, |
||||
}, |
||||
resolve: { |
||||
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'], |
||||
}, |
||||
}; |
@ -1,10 +0,0 @@ |
||||
import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; |
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); |
||||
|
||||
export const plugins = [ |
||||
new ForkTsCheckerWebpackPlugin({ |
||||
logger: 'webpack-infrastructure', |
||||
}), |
||||
]; |
@ -1,19 +0,0 @@ |
||||
import type { Configuration } from 'webpack'; |
||||
|
||||
import { rules } from './webpack.rules'; |
||||
import { plugins } from './webpack.plugins'; |
||||
|
||||
rules.push({ |
||||
test: /\.css$/, |
||||
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], |
||||
}); |
||||
|
||||
export const rendererConfig: Configuration = { |
||||
module: { |
||||
rules, |
||||
}, |
||||
plugins, |
||||
resolve: { |
||||
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'], |
||||
}, |
||||
}; |
@ -1,31 +0,0 @@ |
||||
import type { ModuleOptions } from 'webpack'; |
||||
|
||||
export const rules: Required<ModuleOptions>['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, |
||||
}, |
||||
}, |
||||
}, |
||||
]; |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue