rm test app

rdesktop2
filip mertens 1 year ago
parent 0483d13e56
commit 1d97df570c
  1. 16
      apps/1test/.eslintrc.json
  2. 92
      apps/1test/.gitignore
  3. 35
      apps/1test/forge.config.ts
  4. 55
      apps/1test/package.json
  5. 14
      apps/1test/src/app.tsx
  6. 39
      apps/1test/src/electron/engine.ts
  7. 134
      apps/1test/src/electron/fsPlugin.ts
  8. 53
      apps/1test/src/electron/gitPlugin.ts
  9. 54
      apps/1test/src/electron/lib/electronBasePlugin.ts
  10. 34
      apps/1test/src/electron/lib/electronPluginClient.ts
  11. 18
      apps/1test/src/electron/menus/commands.ts
  12. 36
      apps/1test/src/electron/menus/shell.ts
  13. 75
      apps/1test/src/electron/xtermPlugin.ts
  14. 15
      apps/1test/src/global.d.ts
  15. 7
      apps/1test/src/index.css
  16. 11
      apps/1test/src/index.html
  17. 302
      apps/1test/src/index.ts
  18. 43
      apps/1test/src/preload.ts
  19. 97
      apps/1test/src/remix/fsPlugin.ts
  20. 14
      apps/1test/src/remix/gitPlugin.ts
  21. 176
      apps/1test/src/remix/lib/electronPlugin.ts
  22. 40
      apps/1test/src/remix/ui/remix-ui-filedialog.tsx
  23. 44
      apps/1test/src/remix/ui/remix-ui-xterm.tsx
  24. 95
      apps/1test/src/remix/ui/remix-ui-xterminals.tsx
  25. 11
      apps/1test/src/remix/xtermPlugin.ts
  26. 111
      apps/1test/src/renderer.ts
  27. 20
      apps/1test/tsconfig.json
  28. 18
      apps/1test/webpack.main.config.ts
  29. 10
      apps/1test/webpack.plugins.ts
  30. 19
      apps/1test/webpack.renderer.config.ts
  31. 31
      apps/1test/webpack.rules.ts
  32. 6186
      apps/1test/yarn.lock

@ -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…
Cancel
Save