From 71eef01e5d5c9b644366cc38a80c5ce4e37d464d Mon Sep 17 00:00:00 2001 From: filip mertens Date: Wed, 14 Jun 2023 15:45:55 +0200 Subject: [PATCH] folder handling --- apps/remix-ide/src/app.js | 6 +- .../src/app/plugins/electronConfigPlugin.ts | 19 ++++ apps/remix-ide/src/app/plugins/fsPlugin.ts | 2 +- .../src/app/tabs/locales/en/electron.json | 4 + .../src/app/tabs/locales/en/index.js | 2 + apps/remixdesktop/src/engine.ts | 6 + apps/remixdesktop/src/main.ts | 43 ++++++-- apps/remixdesktop/src/menus/edit.ts | 43 ++++++++ apps/remixdesktop/src/menus/window.ts | 63 +++++++++++ apps/remixdesktop/src/plugins/configPlugin.ts | 17 +-- apps/remixdesktop/src/plugins/fsPlugin.ts | 103 +++++++++++++++--- apps/remixdesktop/src/preload.ts | 2 +- apps/remixdesktop/src/utils/config.ts | 2 + .../panel/src/lib/main/main-panel.tsx | 2 +- .../panel/src/lib/plugins/panel-plugin.tsx | 1 + .../workspace/src/lib/actions/index.ts | 1 + .../workspace/src/lib/actions/payload.ts | 7 ++ .../workspace/src/lib/actions/workspace.ts | 19 +++- .../src/lib/components/electron-menu.tsx | 64 +++++++++++ .../workspace/src/lib/contexts/index.ts | 3 +- .../workspace/src/lib/css/electron-menu.css | 27 +++++ .../src/lib/providers/FileSystemProvider.tsx | 14 ++- .../workspace/src/lib/reducers/workspace.ts | 16 ++- .../workspace/src/lib/remix-ui-workspace.tsx | 12 +- 24 files changed, 415 insertions(+), 63 deletions(-) create mode 100644 apps/remix-ide/src/app/plugins/electronConfigPlugin.ts create mode 100644 apps/remix-ide/src/app/tabs/locales/en/electron.json create mode 100644 apps/remixdesktop/src/menus/edit.ts create mode 100644 apps/remixdesktop/src/menus/window.ts create mode 100644 libs/remix-ui/workspace/src/lib/components/electron-menu.tsx create mode 100644 libs/remix-ui/workspace/src/lib/css/electron-menu.css diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 7c3d9b1662..0d7165412d 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -47,6 +47,7 @@ import { SolidityUmlGen } from './app/plugins/solidity-umlgen' import { ContractFlattener } from './app/plugins/contractFlattener' import { fsPlugin } from './app/plugins/fsPlugin' import { isoGitPlugin } from './app/plugins/isoGitPlugin' +import { electronConfig } from './app/plugins/electronConfigPlugin' const isElectron = require('is-electron') @@ -267,6 +268,7 @@ class AppComponent { const permissionHandler = new PermissionHandlerPlugin() + this.engine.register([ permissionHandler, this.layout, @@ -321,6 +323,8 @@ class AppComponent { this.engine.register([FSPlugin]) const isoGit = new isoGitPlugin() this.engine.register([isoGit]) + const electronConfigPlugin = new electronConfig() + this.engine.register([electronConfigPlugin]) } // LAYOUT & SYSTEM VIEWS @@ -439,7 +443,7 @@ class AppComponent { await this.appManager.activatePlugin(['solidity-script']) if(isElectron()){ - await this.appManager.activatePlugin(['fs', 'isogit']) + await this.appManager.activatePlugin(['fs', 'isogit', 'electronconfig']) } this.appManager.on( diff --git a/apps/remix-ide/src/app/plugins/electronConfigPlugin.ts b/apps/remix-ide/src/app/plugins/electronConfigPlugin.ts new file mode 100644 index 0000000000..3289b4a845 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/electronConfigPlugin.ts @@ -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 { + + } + + +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/fsPlugin.ts b/apps/remix-ide/src/app/plugins/fsPlugin.ts index ea273d65ae..a67fdf8d74 100644 --- a/apps/remix-ide/src/app/plugins/fsPlugin.ts +++ b/apps/remix-ide/src/app/plugins/fsPlugin.ts @@ -16,7 +16,7 @@ export class fsPlugin extends ElectronPlugin { name: '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 // included since it may not exist and must be handled as a special case diff --git a/apps/remix-ide/src/app/tabs/locales/en/electron.json b/apps/remix-ide/src/app/tabs/locales/en/electron.json new file mode 100644 index 0000000000..cdf0a75a59 --- /dev/null +++ b/apps/remix-ide/src/app/tabs/locales/en/electron.json @@ -0,0 +1,4 @@ +{ + "electron.openFolder": "Open Folder", + "electron.recentFolders": "Recent Folders" +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/locales/en/index.js b/apps/remix-ide/src/app/tabs/locales/en/index.js index a5b807a902..e01a36e939 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/index.js +++ b/apps/remix-ide/src/app/tabs/locales/en/index.js @@ -10,6 +10,7 @@ import terminalJson from './terminal.json'; import udappJson from './udapp.json'; import solidityUnitTestingJson from './solidityUnitTesting.json'; import permissionHandlerJson from './permissionHandler.json'; +import electronJson from './electron.json'; export default { ...debuggerJson, @@ -24,4 +25,5 @@ export default { ...udappJson, ...solidityUnitTestingJson, ...permissionHandlerJson, + ...electronJson } diff --git a/apps/remixdesktop/src/engine.ts b/apps/remixdesktop/src/engine.ts index e3a9a2a83d..a0482693c1 100644 --- a/apps/remixdesktop/src/engine.ts +++ b/apps/remixdesktop/src/engine.ts @@ -6,6 +6,7 @@ import { app } from 'electron'; import { XtermPlugin } from './plugins/xtermPlugin'; import git from 'isomorphic-git' import { IsoGitPlugin } from './plugins/isoGitPlugin'; +import { ConfigPlugin } from './plugins/configPlugin'; const engine = new Engine() const appManager = new PluginManager() @@ -13,14 +14,18 @@ const fsPlugin = new FSPlugin() const gitPlugin = new GitPlugin() const xtermPlugin = new XtermPlugin() const isoGitPlugin = new IsoGitPlugin() +const configPlugin = new ConfigPlugin() engine.register(appManager) engine.register(fsPlugin) engine.register(gitPlugin) engine.register(xtermPlugin) engine.register(isoGitPlugin) +engine.register(configPlugin) +appManager.activatePlugin('electronconfig') appManager.activatePlugin('fs') + ipcMain.handle('manager:activatePlugin', async (event, plugin) => { console.log('manager:activatePlugin', plugin, 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 }) + app.on('before-quit', async () => { await appManager.call('fs', 'closeWatch') app.quit() diff --git a/apps/remixdesktop/src/main.ts b/apps/remixdesktop/src/main.ts index f174c50a44..05c380ce14 100644 --- a/apps/remixdesktop/src/main.ts +++ b/apps/remixdesktop/src/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, dialog, Menu } from 'electron'; +import { app, BrowserWindow, dialog, Menu, MenuItem } from 'electron'; import path from 'path'; @@ -16,34 +16,42 @@ if ( // get system home dir const homeDir = app.getPath('userData') -export let mainWindow: BrowserWindow; -export const createWindow = async (): Promise => { +const windowSet = new Set([]); +export const createWindow = async (dir?: string): Promise => { // Create the browser window. - mainWindow = new BrowserWindow({ + const mainWindow = new BrowserWindow({ height: 800, width: 1024, 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. mainWindow.loadURL( - process.env.NODE_ENV === 'production' || isPackaged? `file://${__dirname}/remix-ide/index.html` : - 'http://localhost:8080?opendir=' + homeDir) + process.env.NODE_ENV === 'production' || isPackaged ? `file://${__dirname}/remix-ide/index.html` + params : + 'http://localhost:8080' + params) mainWindow.maximize(); - + + // on close + mainWindow.on('close', (event) => { + console.log('close', event, mainWindow.webContents.id) + windowSet.delete(mainWindow) + }) + + windowSet.add(mainWindow) + // Open the DevTools. mainWindow.webContents.openDevTools(); - + }; // 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', async() => { - await createWindow(); +app.on('ready', async () => { require('./engine') }); @@ -83,6 +91,8 @@ const isMac = process.platform === 'darwin' import FileMenu from './menus/file'; import MainMenu from './menus/main'; import darwinMenu from './menus/darwin'; +import WindowMenu from './menus/window'; +import EditMenu from './menus/edit'; import { execCommand } from './menus/commands'; const commandKeys: Record = { @@ -90,6 +100,15 @@ const commandKeys: Record = { '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)) + + + + + diff --git a/apps/remixdesktop/src/menus/edit.ts b/apps/remixdesktop/src/menus/edit.ts new file mode 100644 index 0000000000..d0b443d215 --- /dev/null +++ b/apps/remixdesktop/src/menus/edit.ts @@ -0,0 +1,43 @@ +import { BrowserWindow, MenuItemConstructorOptions } from 'electron'; + +export default ( + commandKeys: Record, + 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 + }; +}; diff --git a/apps/remixdesktop/src/menus/window.ts b/apps/remixdesktop/src/menus/window.ts new file mode 100644 index 0000000000..30e2ef8125 --- /dev/null +++ b/apps/remixdesktop/src/menus/window.ts @@ -0,0 +1,63 @@ +import { BrowserWindow, MenuItemConstructorOptions } from 'electron'; + +export default ( + commandKeys: Record, + 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 + } +}; diff --git a/apps/remixdesktop/src/plugins/configPlugin.ts b/apps/remixdesktop/src/plugins/configPlugin.ts index cc144f0230..eae7d00d93 100644 --- a/apps/remixdesktop/src/plugins/configPlugin.ts +++ b/apps/remixdesktop/src/plugins/configPlugin.ts @@ -1,6 +1,7 @@ import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" import { Profile } from "@remixproject/plugin-utils"; +import { readConfig, writeConfig } from "../utils/config"; const profile: Profile = { displayName: 'electronconfig', @@ -15,18 +16,12 @@ export class ConfigPlugin extends ElectronBasePlugin { this.methods = [...super.methods, 'writeConfig', 'readConfig'] } - async writeConfig(webContentsId: any, data: any): Promise { - const client = this.clients.find(c => c.webContentsId === webContentsId) - if (client) { - client.writeConfig(data) - } + async writeConfig(data: any): Promise { + writeConfig(data) } async readConfig(webContentsId: any): Promise { - const client = this.clients.find(c => c.webContentsId === webContentsId) - if (client) { - return client.readConfig() - } + return readConfig() } } @@ -48,11 +43,11 @@ class ConfigPluginClient extends ElectronBasePluginClient { } async writeConfig(data: any): Promise { - + writeConfig(data) } async readConfig(): Promise { - + return readConfig() } } \ No newline at end of file diff --git a/apps/remixdesktop/src/plugins/fsPlugin.ts b/apps/remixdesktop/src/plugins/fsPlugin.ts index 63b5153f5e..32aa54a902 100644 --- a/apps/remixdesktop/src/plugins/fsPlugin.ts +++ b/apps/remixdesktop/src/plugins/fsPlugin.ts @@ -3,6 +3,8 @@ import fs from 'fs/promises' import { Profile } from "@remixproject/plugin-utils"; import chokidar from 'chokidar' import { dialog } from "electron"; +import { createWindow } from "../main"; +import { writeConfig } from "../utils/config"; const profile: Profile = { displayName: 'fs', @@ -17,6 +19,31 @@ export class FSPlugin extends ElectronBasePlugin { this.methods = [...super.methods, 'closeWatch'] } + async onActivation(): Promise { + 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 { for (const client of this.clients) { await client.closeWatch() @@ -36,17 +63,20 @@ const clientProfile: Profile = { name: 'fs', displayName: '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 { watcher: chokidar.FSWatcher - workingDir: string = '/Volumes/bunsen/code/empty/' + workingDir: string = '' constructor(webContentsId: number, profile: Profile) { super(webContentsId, profile) this.onload(() => { //console.log('fsPluginClient onload') + this.window.on('close', () => { + this.closeWatch() + }) }) } @@ -141,35 +171,76 @@ class FSPluginClient extends ElectronBasePluginClient { } async closeWatch(): Promise { - //console.log('closing Watcher', this.webContentsId) + console.log('closing Watcher', this.webContentsId) + await this.removeFromOpenedFolders(this.workingDir) if (this.watcher) this.watcher.close() } - async openFolder(): Promise { - const dirs = dialog.showOpenDialogSync(this.window, { - properties: ['openDirectory', 'createDirectory', "showHiddenFiles"] - }) - if (dirs && dirs.length > 0) { - this.workingDir = dirs[0] - this.emit('workingDirChanged', dirs[0]) - } + async updateRecentFolders(path: string): Promise { + const config = await this.call('electronconfig' as any, 'readConfig') + config.recentFolders = config.recentFolders || [] + config.recentFolders = config.recentFolders.filter((p: string) => p !== path) + config.recentFolders.push(path) + writeConfig(config) + } + async updateOpenedFolders(path: string): Promise { + 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 { + 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 { + const config = await this.call('electronconfig' as any, 'readConfig') + return config.recentFolders || [] + } + + + + async openFolder(path?: string): Promise { + 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 { - console.log('setWorkingDir', path) - this.workingDir = path - this.emit('workingDirChanged', path) + console.log('setWorkingDir', path) + this.workingDir = path + await this.updateRecentFolders(path) + await this.updateOpenedFolders(path) + this.window.setTitle(this.workingDir) + this.emit('workingDirChanged', path) } fixPath(path: string): string { + if (this.workingDir === '') throw new Error('workingDir is not set') if (path) { if (path.startsWith('/')) { path = path.slice(1) } } - if (!this.workingDir.endsWith('/')) this.workingDir = this.workingDir + '/' - path = this.workingDir + path + path = this.workingDir + (!this.workingDir.endsWith('/') ? '/' : '') + path return path } diff --git a/apps/remixdesktop/src/preload.ts b/apps/remixdesktop/src/preload.ts index 854049c24a..afdaeaf0ee 100644 --- a/apps/remixdesktop/src/preload.ts +++ b/apps/remixdesktop/src/preload.ts @@ -6,7 +6,7 @@ console.log('preload.ts') /* 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) let webContentsId: number | undefined diff --git a/apps/remixdesktop/src/utils/config.ts b/apps/remixdesktop/src/utils/config.ts index 62e35d45df..7ce7244dcd 100644 --- a/apps/remixdesktop/src/utils/config.ts +++ b/apps/remixdesktop/src/utils/config.ts @@ -4,6 +4,8 @@ import path from 'path' const cacheDir = path.join(os.homedir(), '.cache_remix_ide') +console.log('cacheDir', cacheDir) + try { if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir) diff --git a/libs/remix-ui/panel/src/lib/main/main-panel.tsx b/libs/remix-ui/panel/src/lib/main/main-panel.tsx index 0b59d665e3..126d9ac3ac 100644 --- a/libs/remix-ui/panel/src/lib/main/main-panel.tsx +++ b/libs/remix-ui/panel/src/lib/main/main-panel.tsx @@ -57,7 +57,7 @@ const RemixUIMainPanel = (props: RemixUIMainPanelProps) => { {Object.values(plugins).map((pluginRecord, i) => { return ( - {(pluginRecord.profile.name === 'terminal') ? : null} + {(pluginRecord.profile.name === 'terminal') ? null : null} { const ref:any = panelRef || localRef if (ref.current) { + console.log('ref.current', props.pluginRecord) if (props.pluginRecord.view) { if (React.isValidElement(props.pluginRecord.view)) { setView(props.pluginRecord.view) diff --git a/libs/remix-ui/workspace/src/lib/actions/index.ts b/libs/remix-ui/workspace/src/lib/actions/index.ts index a48524a10c..65a776425c 100644 --- a/libs/remix-ui/workspace/src/lib/actions/index.ts +++ b/libs/remix-ui/workspace/src/lib/actions/index.ts @@ -122,6 +122,7 @@ export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React. console.log('isElectron initWorkspace') plugin.call('notification', 'toast', `connecting to electron...`) if(params.opendir){ + params.opendir = decodeURIComponent(params.opendir) plugin.call('notification', 'toast', `opening ${params.opendir}...`) plugin.call('fs', 'setWorkingDir', params.opendir) } diff --git a/libs/remix-ui/workspace/src/lib/actions/payload.ts b/libs/remix-ui/workspace/src/lib/actions/payload.ts index ea804ce2d1..b1d762bce1 100644 --- a/libs/remix-ui/workspace/src/lib/actions/payload.ts +++ b/libs/remix-ui/workspace/src/lib/actions/payload.ts @@ -292,3 +292,10 @@ export const setGitConfig = (config: {username: string, token: string, email: st payload: config } } + +export const setElectronRecentFolders = (folders: string[]) => { + return { + type: 'SET_ELECTRON_RECENT_FOLDERS', + payload: folders + } +} diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index fa3a0505ad..a887d6f710 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -2,7 +2,7 @@ import React from 'react' import { bufferToHex } from '@ethereumjs/util' import { hash } from '@remix-project/remix-lib' 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 { JSONStandardInput, WorkspaceTemplate } from '../types' @@ -271,9 +271,12 @@ export const fetchWorkspaceDirectory = async (path: string) => { if (!path) return const provider = plugin.fileManager.currentFileProvider() console.log('fetchWorkspaceDirectory', path, provider) - const promise = new Promise((resolve) => { + const promise = new Promise((resolve, reject) => { provider.resolveDirectory(path, (error, fileTree) => { - if (error) console.error(error) + if (error) { + console.error(error) + return reject(error) + } console.log('fetchWorkspaceDirectory', fileTree) resolve(fileTree) }) @@ -735,8 +738,14 @@ export const checkoutRemoteBranch = async (branch: string, remote: string) => { } } -export const openElectronFolder = async () => { - await plugin.call('fs', 'openFolder') +export const openElectronFolder = async (path: string) => { + 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 () => { diff --git a/libs/remix-ui/workspace/src/lib/components/electron-menu.tsx b/libs/remix-ui/workspace/src/lib/components/electron-menu.tsx new file mode 100644 index 0000000000..97b8310a4c --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/components/electron-menu.tsx @@ -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 : + <> +
{await openFolderElectron(null)}} className='btn btn-primary'>
+ {global.fs.browser.recentFolders.length > 0 ? + <> + +
    + {global.fs.browser.recentFolders.map((folder, index) => { + return
  • +
    + {await openFolderElectron(folder)}} className="pl-2 recentfolder_name pr-2">{lastFolderName(folder)} + {await openFolderElectron(folder)}} data-id={{folder}} className="recentfolder_path pr-2">{folder} + { + + }} + className="fas fa-times recentfolder_delete pr-2" + > + + +
    +
  • + })} +
+ + : null} + + ) + ) +} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts index 097e0bd6ea..e05f4f8072 100644 --- a/libs/remix-ui/workspace/src/lib/contexts/index.ts +++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts @@ -45,7 +45,8 @@ export const FileSystemContext = createContext<{ dispatchCreateSolidityGithubAction: () => Promise, dispatchCreateTsSolGithubAction: () => Promise, dispatchCreateSlitherGithubAction: () => Promise - dispatchOpenElectronFolder: () => Promise + dispatchOpenElectronFolder: (path: string) => Promise + dispatchGetElectronRecentFolders: () => Promise }>(null) \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/css/electron-menu.css b/libs/remix-ui/workspace/src/lib/css/electron-menu.css new file mode 100644 index 0000000000..a5b0aff3f0 --- /dev/null +++ b/libs/remix-ui/workspace/src/lib/css/electron-menu.css @@ -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); +} \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index cb07fb1f6a..6ddc33ade3 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -8,7 +8,7 @@ import { browserReducer, browserInitialState } from '../reducers/workspace' import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, uploadFolder, handleDownloadWorkspace, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder, - showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction, openElectronFolder + showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction, openElectronFolder, getElectronRecentFolders } from '../actions' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -183,9 +183,14 @@ export const FileSystemProvider = (props: WorkspaceProps) => { await createSlitherGithubAction() } - const dispatchOpenElectronFolder = async () => { + const dispatchOpenElectronFolder = async (path: string) => { console.log('open electron folder') - await openElectronFolder() + await openElectronFolder(path) + } + + const dispatchGetElectronRecentFolders = async () => { + console.log('get electron recent folders') + await getElectronRecentFolders() } @@ -306,7 +311,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => { dispatchCreateSolidityGithubAction, dispatchCreateTsSolGithubAction, dispatchCreateSlitherGithubAction, - dispatchOpenElectronFolder + dispatchOpenElectronFolder, + dispatchGetElectronRecentFolders } return ( diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index 760354b54e..c51cc18f59 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -34,6 +34,7 @@ export interface BrowserState { error: string }, fileState: fileDecoration[] + recentFolders: string[] }, localhost: { sharedFolder: string, @@ -86,7 +87,8 @@ export const browserInitialState: BrowserState = { removedMenuItems: [], error: null }, - fileState: [] + fileState: [], + recentFolders: [] }, localhost: { 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: throw new Error() } diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index 82bd02ae34..303352a398 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -13,6 +13,7 @@ import { contextMenuActions } from './utils' import FileExplorerContextMenu from './components/file-explorer-context-menu' import { customAction } from '@remixproject/plugin-api' import isElectron from 'is-electron' +import { ElectronMenu } from './components/electron-menu' const _paq = window._paq = window._paq || [] @@ -486,11 +487,6 @@ export function Workspace() { global.dispatchPublishToGist(path, type) } - const openFolderElectron = async () => { - global.dispatchOpenElectronFolder() - } - - const editModeOn = (path: string, type: string, isNew = false) => { if (global.fs.readonly) return global.toast('Cannot write/modify file system in read only mode.') setState(prevState => { @@ -790,14 +786,12 @@ export function Workspace() { - {isElectron() && global.fs.browser.isSuccessfulDirectory ? null : -
Open Folder
- } +
{ toggleDropdown(false) }}>
{(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) &&