folder handling

rdesktop
filip mertens 1 year ago
parent b888c2b894
commit f3ec824c62
  1. 6
      apps/remix-ide/src/app.js
  2. 19
      apps/remix-ide/src/app/plugins/electronConfigPlugin.ts
  3. 2
      apps/remix-ide/src/app/plugins/fsPlugin.ts
  4. 4
      apps/remix-ide/src/app/tabs/locales/en/electron.json
  5. 2
      apps/remix-ide/src/app/tabs/locales/en/index.js
  6. 6
      apps/remixdesktop/src/engine.ts
  7. 43
      apps/remixdesktop/src/main.ts
  8. 43
      apps/remixdesktop/src/menus/edit.ts
  9. 63
      apps/remixdesktop/src/menus/window.ts
  10. 17
      apps/remixdesktop/src/plugins/configPlugin.ts
  11. 103
      apps/remixdesktop/src/plugins/fsPlugin.ts
  12. 2
      apps/remixdesktop/src/preload.ts
  13. 2
      apps/remixdesktop/src/utils/config.ts
  14. 2
      libs/remix-ui/panel/src/lib/main/main-panel.tsx
  15. 1
      libs/remix-ui/panel/src/lib/plugins/panel-plugin.tsx
  16. 1
      libs/remix-ui/workspace/src/lib/actions/index.ts
  17. 7
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  18. 19
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  19. 64
      libs/remix-ui/workspace/src/lib/components/electron-menu.tsx
  20. 3
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  21. 27
      libs/remix-ui/workspace/src/lib/css/electron-menu.css
  22. 14
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  23. 16
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  24. 12
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx

@ -47,6 +47,7 @@ import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
import { ContractFlattener } from './app/plugins/contractFlattener' import { ContractFlattener } from './app/plugins/contractFlattener'
import { fsPlugin } from './app/plugins/fsPlugin' import { fsPlugin } from './app/plugins/fsPlugin'
import { isoGitPlugin } from './app/plugins/isoGitPlugin' import { isoGitPlugin } from './app/plugins/isoGitPlugin'
import { electronConfig } from './app/plugins/electronConfigPlugin'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -267,6 +268,7 @@ class AppComponent {
const permissionHandler = new PermissionHandlerPlugin() const permissionHandler = new PermissionHandlerPlugin()
this.engine.register([ this.engine.register([
permissionHandler, permissionHandler,
this.layout, this.layout,
@ -321,6 +323,8 @@ class AppComponent {
this.engine.register([FSPlugin]) this.engine.register([FSPlugin])
const isoGit = new isoGitPlugin() const isoGit = new isoGitPlugin()
this.engine.register([isoGit]) this.engine.register([isoGit])
const electronConfigPlugin = new electronConfig()
this.engine.register([electronConfigPlugin])
} }
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
@ -439,7 +443,7 @@ class AppComponent {
await this.appManager.activatePlugin(['solidity-script']) await this.appManager.activatePlugin(['solidity-script'])
if(isElectron()){ if(isElectron()){
await this.appManager.activatePlugin(['fs', 'isogit']) await this.appManager.activatePlugin(['fs', 'isogit', 'electronconfig'])
} }
this.appManager.on( this.appManager.on(

@ -0,0 +1,19 @@
import { ElectronPlugin } from '@remixproject/engine-electron';
export class electronConfig extends ElectronPlugin {
constructor() {
super({
displayName: 'electronconfig',
name: 'electronconfig',
description: 'electronconfig',
})
this.methods = []
}
onActivation(): void {
}
}

@ -16,7 +16,7 @@ export class fsPlugin extends ElectronPlugin {
name: 'fs', name: 'fs',
description: 'fs', description: 'fs',
}) })
this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'lstat', 'exists', 'setWorkingDir'] this.methods = ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'lstat', 'exists', 'setWorkingDir', 'getRecentFolders']
// List of commands all filesystems are expected to provide. `rm` is not // List of commands all filesystems are expected to provide. `rm` is not
// included since it may not exist and must be handled as a special case // included since it may not exist and must be handled as a special case

@ -0,0 +1,4 @@
{
"electron.openFolder": "Open Folder",
"electron.recentFolders": "Recent Folders"
}

@ -10,6 +10,7 @@ import terminalJson from './terminal.json';
import udappJson from './udapp.json'; import udappJson from './udapp.json';
import solidityUnitTestingJson from './solidityUnitTesting.json'; import solidityUnitTestingJson from './solidityUnitTesting.json';
import permissionHandlerJson from './permissionHandler.json'; import permissionHandlerJson from './permissionHandler.json';
import electronJson from './electron.json';
export default { export default {
...debuggerJson, ...debuggerJson,
@ -24,4 +25,5 @@ export default {
...udappJson, ...udappJson,
...solidityUnitTestingJson, ...solidityUnitTestingJson,
...permissionHandlerJson, ...permissionHandlerJson,
...electronJson
} }

@ -6,6 +6,7 @@ import { app } from 'electron';
import { XtermPlugin } from './plugins/xtermPlugin'; import { XtermPlugin } from './plugins/xtermPlugin';
import git from 'isomorphic-git' import git from 'isomorphic-git'
import { IsoGitPlugin } from './plugins/isoGitPlugin'; import { IsoGitPlugin } from './plugins/isoGitPlugin';
import { ConfigPlugin } from './plugins/configPlugin';
const engine = new Engine() const engine = new Engine()
const appManager = new PluginManager() const appManager = new PluginManager()
@ -13,14 +14,18 @@ const fsPlugin = new FSPlugin()
const gitPlugin = new GitPlugin() const gitPlugin = new GitPlugin()
const xtermPlugin = new XtermPlugin() const xtermPlugin = new XtermPlugin()
const isoGitPlugin = new IsoGitPlugin() const isoGitPlugin = new IsoGitPlugin()
const configPlugin = new ConfigPlugin()
engine.register(appManager) engine.register(appManager)
engine.register(fsPlugin) engine.register(fsPlugin)
engine.register(gitPlugin) engine.register(gitPlugin)
engine.register(xtermPlugin) engine.register(xtermPlugin)
engine.register(isoGitPlugin) engine.register(isoGitPlugin)
engine.register(configPlugin)
appManager.activatePlugin('electronconfig')
appManager.activatePlugin('fs') appManager.activatePlugin('fs')
ipcMain.handle('manager:activatePlugin', async (event, plugin) => { ipcMain.handle('manager:activatePlugin', async (event, plugin) => {
console.log('manager:activatePlugin', plugin, event.sender.id) console.log('manager:activatePlugin', plugin, event.sender.id)
return await appManager.call(plugin, 'createClient', event.sender.id) return await appManager.call(plugin, 'createClient', event.sender.id)
@ -35,6 +40,7 @@ ipcMain.handle('getWebContentsID', (event, message) => {
return event.sender.id return event.sender.id
}) })
app.on('before-quit', async () => { app.on('before-quit', async () => {
await appManager.call('fs', 'closeWatch') await appManager.call('fs', 'closeWatch')
app.quit() app.quit()

@ -1,4 +1,4 @@
import { app, BrowserWindow, dialog, Menu } from 'electron'; import { app, BrowserWindow, dialog, Menu, MenuItem } from 'electron';
import path from 'path'; import path from 'path';
@ -16,34 +16,42 @@ if (
// get system home dir // get system home dir
const homeDir = app.getPath('userData') const homeDir = app.getPath('userData')
export let mainWindow: BrowserWindow; const windowSet = new Set<BrowserWindow>([]);
export const createWindow = async (): Promise<void> => { export const createWindow = async (dir?: string): Promise<void> => {
// Create the browser window. // Create the browser window.
mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
height: 800, height: 800,
width: 1024, width: 1024,
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js') preload: path.join(__dirname, 'preload.js')
}, },
}); });
let params = dir ? `?opendir=${encodeURIComponent(dir)}` : '';
// and load the index.html of the app. // and load the index.html of the app.
mainWindow.loadURL( mainWindow.loadURL(
process.env.NODE_ENV === 'production' || isPackaged? `file://${__dirname}/remix-ide/index.html` : process.env.NODE_ENV === 'production' || isPackaged ? `file://${__dirname}/remix-ide/index.html` + params :
'http://localhost:8080?opendir=' + homeDir) 'http://localhost:8080' + params)
mainWindow.maximize(); mainWindow.maximize();
// on close
mainWindow.on('close', (event) => {
console.log('close', event, mainWindow.webContents.id)
windowSet.delete(mainWindow)
})
windowSet.add(mainWindow)
// Open the DevTools. // Open the DevTools.
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
}; };
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
app.on('ready', async() => { app.on('ready', async () => {
await createWindow();
require('./engine') require('./engine')
}); });
@ -83,6 +91,8 @@ const isMac = process.platform === 'darwin'
import FileMenu from './menus/file'; import FileMenu from './menus/file';
import MainMenu from './menus/main'; import MainMenu from './menus/main';
import darwinMenu from './menus/darwin'; import darwinMenu from './menus/darwin';
import WindowMenu from './menus/window';
import EditMenu from './menus/edit';
import { execCommand } from './menus/commands'; import { execCommand } from './menus/commands';
const commandKeys: Record<string, string> = { const commandKeys: Record<string, string> = {
@ -90,6 +100,15 @@ const commandKeys: Record<string, string> = {
'folder:open': 'CmdOrCtrl+O', 'folder:open': 'CmdOrCtrl+O',
}; };
const menu = [...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []),
FileMenu(commandKeys, execCommand),
EditMenu(commandKeys, execCommand),
WindowMenu(commandKeys, execCommand, [])
]
const menu = [...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []), FileMenu(commandKeys, execCommand)]
Menu.setApplicationMenu(Menu.buildFromTemplate(menu)) Menu.setApplicationMenu(Menu.buildFromTemplate(menu))

@ -0,0 +1,43 @@
import { BrowserWindow, MenuItemConstructorOptions } from 'electron';
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
) => {
const submenu: MenuItemConstructorOptions[] = [
{
label: 'Cut',
accelerator: commandKeys['editor:cut'],
enabled: false
},
{
role: 'copy',
command: 'editor:copy',
accelerator: commandKeys['editor:copy'],
registerAccelerator: true
} as any,
{
role: 'paste',
accelerator: commandKeys['editor:paste'],
registerAccelerator: true
}
];
if (process.platform !== 'darwin') {
submenu.push(
{ type: 'separator' },
{
label: 'Preferences...',
accelerator: commandKeys['window:preferences'],
click() {
execCommand('window:preferences');
}
}
);
}
return {
label: 'Edit',
submenu
};
};

@ -0,0 +1,63 @@
import { BrowserWindow, MenuItemConstructorOptions } from 'electron';
export default (
commandKeys: Record<string, string>,
execCommand: (command: string, focusedWindow?: BrowserWindow) => void,
openedWindows: BrowserWindow[]
): MenuItemConstructorOptions => {
const submenu: MenuItemConstructorOptions[] = [
{
role: 'minimize',
accelerator: commandKeys['window:minimize']
},
{
type: 'separator'
},
{
// It's the same thing as clicking the green traffc-light on macOS
role: 'zoom',
accelerator: commandKeys['window:zoom']
},
{
type: 'separator'
},
{
type: 'separator'
},
{
role: 'front'
},
{
label: 'Toggle Always on Top',
click: (item, focusedWindow) => {
execCommand('window:toggleKeepOnTop', focusedWindow);
}
},
{
role: 'togglefullscreen',
accelerator: commandKeys['window:toggleFullScreen']
},
{
type: 'separator'
},
]
if(openedWindows.length > 1) {
submenu.push({
label: 'Close',
accelerator: commandKeys['pane:close'],
click(item, focusedWindow) {
execCommand('pane:close', focusedWindow);
}
})
}
return {
role: 'window',
id: 'window',
submenu
}
};

@ -1,6 +1,7 @@
import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron"
import { Profile } from "@remixproject/plugin-utils"; import { Profile } from "@remixproject/plugin-utils";
import { readConfig, writeConfig } from "../utils/config";
const profile: Profile = { const profile: Profile = {
displayName: 'electronconfig', displayName: 'electronconfig',
@ -15,18 +16,12 @@ export class ConfigPlugin extends ElectronBasePlugin {
this.methods = [...super.methods, 'writeConfig', 'readConfig'] this.methods = [...super.methods, 'writeConfig', 'readConfig']
} }
async writeConfig(webContentsId: any, data: any): Promise<void> { async writeConfig(data: any): Promise<void> {
const client = this.clients.find(c => c.webContentsId === webContentsId) writeConfig(data)
if (client) {
client.writeConfig(data)
}
} }
async readConfig(webContentsId: any): Promise<any> { async readConfig(webContentsId: any): Promise<any> {
const client = this.clients.find(c => c.webContentsId === webContentsId) return readConfig()
if (client) {
return client.readConfig()
}
} }
} }
@ -48,11 +43,11 @@ class ConfigPluginClient extends ElectronBasePluginClient {
} }
async writeConfig(data: any): Promise<void> { async writeConfig(data: any): Promise<void> {
writeConfig(data)
} }
async readConfig(): Promise<any> { async readConfig(): Promise<any> {
return readConfig()
} }
} }

@ -3,6 +3,8 @@ import fs from 'fs/promises'
import { Profile } from "@remixproject/plugin-utils"; import { Profile } from "@remixproject/plugin-utils";
import chokidar from 'chokidar' import chokidar from 'chokidar'
import { dialog } from "electron"; import { dialog } from "electron";
import { createWindow } from "../main";
import { writeConfig } from "../utils/config";
const profile: Profile = { const profile: Profile = {
displayName: 'fs', displayName: 'fs',
@ -17,6 +19,31 @@ export class FSPlugin extends ElectronBasePlugin {
this.methods = [...super.methods, 'closeWatch'] this.methods = [...super.methods, 'closeWatch']
} }
async onActivation(): Promise<void> {
const config = await this.call('electronconfig' as any, 'readConfig')
const openedFolders = config && config.openedFolders || []
this.call('electronconfig', 'writeConfig', { 'openedFolders': openedFolders })
const foldersToDelete: string[] = []
if (openedFolders && openedFolders.length) {
for (const folder of openedFolders) {
console.log('opening folder', folder)
try {
const stat = await fs.stat(folder)
if (stat.isDirectory()) {
createWindow(folder)
}
} catch (e) {
console.log('error opening folder', folder, e)
foldersToDelete.push(folder)
}
}
if (foldersToDelete.length) {
const newFolders = openedFolders.filter((f: string) => !foldersToDelete.includes(f))
this.call('electronconfig', 'writeConfig', { 'recentFolders': newFolders })
}
}
}
async closeWatch(): Promise<void> { async closeWatch(): Promise<void> {
for (const client of this.clients) { for (const client of this.clients) {
await client.closeWatch() await client.closeWatch()
@ -36,17 +63,20 @@ const clientProfile: Profile = {
name: 'fs', name: 'fs',
displayName: 'fs', displayName: 'fs',
description: 'fs', description: 'fs',
methods: ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'lstat', 'exists', 'currentPath', 'watch', 'closeWatch', 'setWorkingDir', 'openFolder'] methods: ['readdir', 'readFile', 'writeFile', 'mkdir', 'rmdir', 'unlink', 'rename', 'stat', 'lstat', 'exists', 'currentPath', 'watch', 'closeWatch', 'setWorkingDir', 'openFolder', 'getRecentFolders']
} }
class FSPluginClient extends ElectronBasePluginClient { class FSPluginClient extends ElectronBasePluginClient {
watcher: chokidar.FSWatcher watcher: chokidar.FSWatcher
workingDir: string = '/Volumes/bunsen/code/empty/' workingDir: string = ''
constructor(webContentsId: number, profile: Profile) { constructor(webContentsId: number, profile: Profile) {
super(webContentsId, profile) super(webContentsId, profile)
this.onload(() => { this.onload(() => {
//console.log('fsPluginClient onload') //console.log('fsPluginClient onload')
this.window.on('close', () => {
this.closeWatch()
})
}) })
} }
@ -141,35 +171,76 @@ class FSPluginClient extends ElectronBasePluginClient {
} }
async closeWatch(): Promise<void> { async closeWatch(): Promise<void> {
//console.log('closing Watcher', this.webContentsId) console.log('closing Watcher', this.webContentsId)
await this.removeFromOpenedFolders(this.workingDir)
if (this.watcher) this.watcher.close() if (this.watcher) this.watcher.close()
} }
async openFolder(): Promise<void> { async updateRecentFolders(path: string): Promise<void> {
const dirs = dialog.showOpenDialogSync(this.window, { const config = await this.call('electronconfig' as any, 'readConfig')
properties: ['openDirectory', 'createDirectory', "showHiddenFiles"] config.recentFolders = config.recentFolders || []
}) config.recentFolders = config.recentFolders.filter((p: string) => p !== path)
if (dirs && dirs.length > 0) { config.recentFolders.push(path)
this.workingDir = dirs[0] writeConfig(config)
this.emit('workingDirChanged', dirs[0]) }
}
async updateOpenedFolders(path: string): Promise<void> {
const config = await this.call('electronconfig' as any, 'readConfig')
config.openedFolders = config.openedFolders || []
config.openedFolders = config.openedFolders.filter((p: string) => p !== path)
config.openedFolders.push(path)
writeConfig(config)
}
async removeFromOpenedFolders(path: string): Promise<void> {
console.log('removeFromOpenedFolders', path)
const config = await this.call('electronconfig' as any, 'readConfig')
config.openedFolders = config.openedFolders || []
config.openedFolders = config.openedFolders.filter((p: string) => p !== path)
console.log('removeFromOpenedFolders', config)
writeConfig(config)
}
async getRecentFolders(): Promise<string[]> {
const config = await this.call('electronconfig' as any, 'readConfig')
return config.recentFolders || []
}
async openFolder(path?: string): Promise<void> {
let dirs: string[] | undefined
if (!path) {
dirs = dialog.showOpenDialogSync(this.window, {
properties: ['openDirectory', 'createDirectory', "showHiddenFiles"]
})
}
path = dirs && dirs.length && dirs[0] ? dirs[0] : path
if (!path) return
this.workingDir = path
await this.updateRecentFolders(path)
await this.updateOpenedFolders(path)
this.window.setTitle(this.workingDir)
this.emit('workingDirChanged', path)
} }
async setWorkingDir(path: string): Promise<void> { async setWorkingDir(path: string): Promise<void> {
console.log('setWorkingDir', path) console.log('setWorkingDir', path)
this.workingDir = path this.workingDir = path
this.emit('workingDirChanged', path) await this.updateRecentFolders(path)
await this.updateOpenedFolders(path)
this.window.setTitle(this.workingDir)
this.emit('workingDirChanged', path)
} }
fixPath(path: string): string { fixPath(path: string): string {
if (this.workingDir === '') throw new Error('workingDir is not set')
if (path) { if (path) {
if (path.startsWith('/')) { if (path.startsWith('/')) {
path = path.slice(1) path = path.slice(1)
} }
} }
if (!this.workingDir.endsWith('/')) this.workingDir = this.workingDir + '/' path = this.workingDir + (!this.workingDir.endsWith('/') ? '/' : '') + path
path = this.workingDir + path
return path return path
} }

@ -6,7 +6,7 @@ console.log('preload.ts')
/* preload script needs statically defined API for each plugin */ /* preload script needs statically defined API for each plugin */
const exposedPLugins = ['fs', 'git', 'xterm', 'isogit'] const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig']
console.log('preload.ts', process) console.log('preload.ts', process)
let webContentsId: number | undefined let webContentsId: number | undefined

@ -4,6 +4,8 @@ import path from 'path'
const cacheDir = path.join(os.homedir(), '.cache_remix_ide') const cacheDir = path.join(os.homedir(), '.cache_remix_ide')
console.log('cacheDir', cacheDir)
try { try {
if (!fs.existsSync(cacheDir)) { if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir) fs.mkdirSync(cacheDir)

@ -57,7 +57,7 @@ const RemixUIMainPanel = (props: RemixUIMainPanelProps) => {
{Object.values(plugins).map((pluginRecord, i) => { {Object.values(plugins).map((pluginRecord, i) => {
return ( return (
<React.Fragment key={`mainView${i}`}> <React.Fragment key={`mainView${i}`}>
{(pluginRecord.profile.name === 'terminal') ? <DragBar key='dragbar-terminal' hidden={pluginRecord.minimized || false} setHideStatus={showTerminal} refObject={terminalRef}></DragBar> : null} {(pluginRecord.profile.name === 'terminal') ? null : null}
<RemixUIPanelPlugin <RemixUIPanelPlugin
ref={refs[i]} ref={refs[i]}
key={pluginRecord.profile.name} key={pluginRecord.profile.name}

@ -13,6 +13,7 @@ const RemixUIPanelPlugin = (props: panelPLuginProps, panelRef: any) => {
const ref:any = panelRef || localRef const ref:any = panelRef || localRef
if (ref.current) { if (ref.current) {
console.log('ref.current', props.pluginRecord)
if (props.pluginRecord.view) { if (props.pluginRecord.view) {
if (React.isValidElement(props.pluginRecord.view)) { if (React.isValidElement(props.pluginRecord.view)) {
setView(props.pluginRecord.view) setView(props.pluginRecord.view)

@ -122,6 +122,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.
console.log('isElectron initWorkspace') console.log('isElectron initWorkspace')
plugin.call('notification', 'toast', `connecting to electron...`) plugin.call('notification', 'toast', `connecting to electron...`)
if(params.opendir){ if(params.opendir){
params.opendir = decodeURIComponent(params.opendir)
plugin.call('notification', 'toast', `opening ${params.opendir}...`) plugin.call('notification', 'toast', `opening ${params.opendir}...`)
plugin.call('fs', 'setWorkingDir', params.opendir) plugin.call('fs', 'setWorkingDir', params.opendir)
} }

@ -292,3 +292,10 @@ export const setGitConfig = (config: {username: string, token: string, email: st
payload: config payload: config
} }
} }
export const setElectronRecentFolders = (folders: string[]) => {
return {
type: 'SET_ELECTRON_RECENT_FOLDERS',
payload: folders
}
}

@ -2,7 +2,7 @@ import React from 'react'
import { bufferToHex } from '@ethereumjs/util' import { bufferToHex } from '@ethereumjs/util'
import { hash } from '@remix-project/remix-lib' import { hash } from '@remix-project/remix-lib'
import axios, { AxiosResponse } from 'axios' import axios, { AxiosResponse } from 'axios'
import { addInputFieldSuccess, cloneRepositoryFailed, cloneRepositoryRequest, cloneRepositorySuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setCurrentWorkspaceBranches, setCurrentWorkspaceCurrentBranch, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace, setCurrentWorkspaceIsGitRepo, setGitConfig } from './payload' import { addInputFieldSuccess, cloneRepositoryFailed, cloneRepositoryRequest, cloneRepositorySuccess, createWorkspaceError, createWorkspaceRequest, createWorkspaceSuccess, displayNotification, displayPopUp, fetchWorkspaceDirectoryError, fetchWorkspaceDirectoryRequest, fetchWorkspaceDirectorySuccess, hideNotification, setCurrentWorkspace, setCurrentWorkspaceBranches, setCurrentWorkspaceCurrentBranch, setDeleteWorkspace, setMode, setReadOnlyMode, setRenameWorkspace, setCurrentWorkspaceIsGitRepo, setGitConfig, setElectronRecentFolders } from './payload'
import { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper' import { addSlash, checkSlash, checkSpecialChars } from '@remix-ui/helper'
import { JSONStandardInput, WorkspaceTemplate } from '../types' import { JSONStandardInput, WorkspaceTemplate } from '../types'
@ -272,9 +272,12 @@ export const fetchWorkspaceDirectory = async (path: string) => {
if (!path) return if (!path) return
const provider = plugin.fileManager.currentFileProvider() const provider = plugin.fileManager.currentFileProvider()
console.log('fetchWorkspaceDirectory', path, provider) console.log('fetchWorkspaceDirectory', path, provider)
const promise = new Promise((resolve) => { const promise = new Promise((resolve, reject) => {
provider.resolveDirectory(path, (error, fileTree) => { provider.resolveDirectory(path, (error, fileTree) => {
if (error) console.error(error) if (error) {
console.error(error)
return reject(error)
}
console.log('fetchWorkspaceDirectory', fileTree) console.log('fetchWorkspaceDirectory', fileTree)
resolve(fileTree) resolve(fileTree)
}) })
@ -747,8 +750,14 @@ export const checkoutRemoteBranch = async (branch: string, remote: string) => {
} }
} }
export const openElectronFolder = async () => { export const openElectronFolder = async (path: string) => {
await plugin.call('fs', 'openFolder') await plugin.call('fs', 'openFolder', path)
}
export const getElectronRecentFolders = async () => {
const folders = await plugin.call('fs', 'getRecentFolders')
dispatch(setElectronRecentFolders(folders))
return folders
} }
export const hasLocalChanges = async () => { export const hasLocalChanges = async () => {

@ -0,0 +1,64 @@
import React, { MouseEventHandler, useContext, useEffect, useState } from "react"
import { FileSystemContext } from "../contexts"
import isElectron from 'is-electron'
import { FormattedMessage } from "react-intl"
import '../css/electron-menu.css'
export const ElectronMenu = () => {
const global = useContext(FileSystemContext)
useEffect(() => {
if (isElectron()) {
global.dispatchGetElectronRecentFolders()
}
}, [])
useEffect(() => {
if (isElectron()) {
console.log('global.fs.browser.recentFolders', global.fs.browser.recentFolders)
}
}, [global.fs.browser.recentFolders])
const openFolderElectron = async (path: string) => {
console.log('open folder electron', path)
global.dispatchOpenElectronFolder(path)
}
const lastFolderName = (path: string) => {
const pathArray = path.split('/')
return pathArray[pathArray.length - 1]
}
return (
(isElectron() && global.fs.browser.isSuccessfulWorkspace ? null :
<>
<div onClick={async()=>{await openFolderElectron(null)}} className='btn btn-primary'><FormattedMessage id="electron.openFolder" /></div>
{global.fs.browser.recentFolders.length > 0 ?
<>
<label className="py-2 pt-3 align-self-center m-0" style={{ fontSize: "1.2rem" }}>
<FormattedMessage id="electron.recentFolders" />
</label>
<ul>
{global.fs.browser.recentFolders.map((folder, index) => {
return <li key={index}>
<div className="recentfolder pb-1">
<span onClick={async()=>{await openFolderElectron(folder)}} className="pl-2 recentfolder_name pr-2">{lastFolderName(folder)}</span>
<span onClick={async()=>{await openFolderElectron(folder)}} data-id={{folder}} className="recentfolder_path pr-2">{folder}</span>
<i
onClick={() => {
}}
className="fas fa-times recentfolder_delete pr-2"
>
</i>
</div>
</li>
})}
</ul>
</>
: null}
</>
)
)
}

@ -46,7 +46,8 @@ export const FileSystemContext = createContext<{
dispatchCreateTsSolGithubAction: () => Promise<void>, dispatchCreateTsSolGithubAction: () => Promise<void>,
dispatchCreateSlitherGithubAction: () => Promise<void> dispatchCreateSlitherGithubAction: () => Promise<void>
dispatchCreateHelperScripts: (script: string) => Promise<void> dispatchCreateHelperScripts: (script: string) => Promise<void>
dispatchOpenElectronFolder: () => Promise<void> dispatchOpenElectronFolder: (path: string) => Promise<void>
dispatchGetElectronRecentFolders: () => Promise<void>
}>(null) }>(null)

@ -0,0 +1,27 @@
.recentfolder {
display: flex;
min-width: 0;
cursor: pointer;
}
.recentfolder_path {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.recentfolder_name {
flex-shrink: 0;
color: var(--text);
}
.recentfolder_name:hover {
color: var(--primary);
text-decoration: underline;
}
.recentfolder_delete {
flex-shrink: 0;
margin-left: auto;
color: var(--text);
}

@ -8,7 +8,7 @@ import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, uploadFolder, handleDownloadWorkspace, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, uploadFolder, handleDownloadWorkspace, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder,
showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction, createHelperScripts, openElectronFolder showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction, createHelperScripts, openElectronFolder, getElectronRecentFolders
} from '../actions' } from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -187,9 +187,14 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await createHelperScripts(script) await createHelperScripts(script)
} }
const dispatchOpenElectronFolder = async () => { const dispatchOpenElectronFolder = async (path: string) => {
console.log('open electron folder') console.log('open electron folder')
await openElectronFolder() await openElectronFolder(path)
}
const dispatchGetElectronRecentFolders = async () => {
console.log('get electron recent folders')
await getElectronRecentFolders()
} }
@ -311,7 +316,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchCreateTsSolGithubAction, dispatchCreateTsSolGithubAction,
dispatchCreateSlitherGithubAction, dispatchCreateSlitherGithubAction,
dispatchCreateHelperScripts, dispatchCreateHelperScripts,
dispatchOpenElectronFolder dispatchOpenElectronFolder,
dispatchGetElectronRecentFolders
} }
return ( return (
<FileSystemContext.Provider value={value}> <FileSystemContext.Provider value={value}>

@ -34,6 +34,7 @@ export interface BrowserState {
error: string error: string
}, },
fileState: fileDecoration[] fileState: fileDecoration[]
recentFolders: string[]
}, },
localhost: { localhost: {
sharedFolder: string, sharedFolder: string,
@ -86,7 +87,8 @@ export const browserInitialState: BrowserState = {
removedMenuItems: [], removedMenuItems: [],
error: null error: null
}, },
fileState: [] fileState: [],
recentFolders: []
}, },
localhost: { localhost: {
sharedFolder: '', sharedFolder: '',
@ -728,6 +730,18 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
} }
} }
case 'SET_ELECTRON_RECENT_FOLDERS': {
const payload: string[] = action.payload
return {
...state,
browser: {
...state.browser,
recentFolders: payload
}
}
}
default: default:
throw new Error() throw new Error()
} }

@ -13,6 +13,7 @@ import { contextMenuActions } from './utils'
import FileExplorerContextMenu from './components/file-explorer-context-menu' import FileExplorerContextMenu from './components/file-explorer-context-menu'
import { customAction } from '@remixproject/plugin-api' import { customAction } from '@remixproject/plugin-api'
import isElectron from 'is-electron' import isElectron from 'is-electron'
import { ElectronMenu } from './components/electron-menu'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
@ -492,11 +493,6 @@ export function Workspace() {
global.dispatchPublishToGist(path, type) global.dispatchPublishToGist(path, type)
} }
const openFolderElectron = async () => {
global.dispatchOpenElectronFolder()
}
const editModeOn = (path: string, type: string, isNew = false) => { const editModeOn = (path: string, type: string, isNew = false) => {
if (global.fs.readonly) return global.toast('Cannot write/modify file system in read only mode.') if (global.fs.readonly) return global.toast('Cannot write/modify file system in read only mode.')
setState(prevState => { setState(prevState => {
@ -796,14 +792,12 @@ export function Workspace() {
</div> </div>
</header> </header>
</div> </div>
{isElectron() && global.fs.browser.isSuccessfulDirectory ? null : <ElectronMenu></ElectronMenu>
<div onClick={openFolderElectron} className='btn btn-primary'>Open Folder</div>
}
<div className='h-100 remixui_fileExplorerTree' onFocus={() => { toggleDropdown(false) }}> <div className='h-100 remixui_fileExplorerTree' onFocus={() => { toggleDropdown(false) }}>
<div className='h-100'> <div className='h-100'>
{(global.fs.browser.isRequestingWorkspace || global.fs.browser.isRequestingCloning) && <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div>} {(global.fs.browser.isRequestingWorkspace || global.fs.browser.isRequestingCloning) && <div className="text-center py-5"><i className="fas fa-spinner fa-pulse fa-2x"></i></div>}
{!(global.fs.browser.isRequestingWorkspace || global.fs.browser.isRequestingCloning) && {!(global.fs.browser.isRequestingWorkspace || global.fs.browser.isRequestingCloning) &&
(global.fs.mode === 'browser') && (currentWorkspace !== NO_WORKSPACE) && (global.fs.mode === 'browser') && (currentWorkspace !== NO_WORKSPACE) && (!isElectron() || global.fs.browser.isSuccessfulWorkspace) &&
<div className='h-100 remixui_treeview' data-id='filePanelFileExplorerTree'> <div className='h-100 remixui_treeview' data-id='filePanelFileExplorerTree'>
<FileExplorer <FileExplorer
fileState={global.fs.browser.fileState} fileState={global.fs.browser.fileState}

Loading…
Cancel
Save