diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index d24f7ee7fd..fee2b2cb23 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -132,7 +132,10 @@ module.exports = class Filepanel extends ViewPlugin { } async createNewFile () { - return await this.request.createNewFile() + const provider = this.fileManager.currentFileProvider() + const dir = provider.workspace || '/' + + this.emit('displayNewFileInput', dir) } async uploadFile (event) { diff --git a/libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts b/libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts deleted file mode 100644 index 8813d610e1..0000000000 --- a/libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts +++ /dev/null @@ -1,384 +0,0 @@ -import React from 'react' -import { File } from '../types' -import { extractNameFromKey, extractParentFromKey } from '../utils' - -const queuedEvents = [] -const pendingEvents = {} -let provider = null -let plugin = null -let dispatch: React.Dispatch = null - -export const fetchDirectoryError = (error: any) => { - return { - type: 'FETCH_DIRECTORY_ERROR', - payload: error - } -} - -export const fetchDirectoryRequest = (promise: Promise) => { - return { - type: 'FETCH_DIRECTORY_REQUEST', - payload: promise - } -} - -export const fetchDirectorySuccess = (path: string, files: File[]) => { - return { - type: 'FETCH_DIRECTORY_SUCCESS', - payload: { path, files } - } -} - -export const fileSystemReset = () => { - return { - type: 'FILESYSTEM_RESET' - } -} - -const normalize = (parent, filesList, newInputType?: string): any => { - const folders = {} - const files = {} - - Object.keys(filesList || {}).forEach(key => { - key = key.replace(/^\/|\/$/g, '') // remove first and last slash - let path = key - path = path.replace(/^\/|\/$/g, '') // remove first and last slash - - if (filesList[key].isDirectory) { - folders[extractNameFromKey(key)] = { - path, - name: extractNameFromKey(path).indexOf('gist-') === 0 ? extractNameFromKey(path).split('-')[1] : extractNameFromKey(path), - isDirectory: filesList[key].isDirectory, - type: extractNameFromKey(path).indexOf('gist-') === 0 ? 'gist' : 'folder' - } - } else { - files[extractNameFromKey(key)] = { - path, - name: extractNameFromKey(path), - isDirectory: filesList[key].isDirectory, - type: 'file' - } - } - }) - - if (newInputType === 'folder') { - const path = parent + '/blank' - - folders[path] = { - path: path, - name: '', - isDirectory: true, - type: 'folder' - } - } else if (newInputType === 'file') { - const path = parent + '/blank' - - files[path] = { - path: path, - name: '', - isDirectory: false, - type: 'file' - } - } - - return Object.assign({}, folders, files) -} - -const fetchDirectoryContent = async (provider, folderPath: string, newInputType?: string): Promise => { - return new Promise((resolve) => { - provider.resolveDirectory(folderPath, (error, fileTree) => { - if (error) console.error(error) - const files = normalize(folderPath, fileTree, newInputType) - - resolve({ [extractNameFromKey(folderPath)]: files }) - }) - }) -} - -export const fetchDirectory = (provider, path: string) => (dispatch: React.Dispatch) => { - const promise = fetchDirectoryContent(provider, path) - - dispatch(fetchDirectoryRequest(promise)) - promise.then((files) => { - dispatch(fetchDirectorySuccess(path, files)) - }).catch((error) => { - dispatch(fetchDirectoryError({ error })) - }) - return promise -} - -export const resolveDirectoryError = (error: any) => { - return { - type: 'RESOLVE_DIRECTORY_ERROR', - payload: error - } -} - -export const resolveDirectoryRequest = (promise: Promise) => { - return { - type: 'RESOLVE_DIRECTORY_REQUEST', - payload: promise - } -} - -export const resolveDirectorySuccess = (path: string, files: File[]) => { - return { - type: 'RESOLVE_DIRECTORY_SUCCESS', - payload: { path, files } - } -} - -export const resolveDirectory = (provider, path: string) => (dispatch: React.Dispatch) => { - const promise = fetchDirectoryContent(provider, path) - - dispatch(resolveDirectoryRequest(promise)) - promise.then((files) => { - dispatch(resolveDirectorySuccess(path, files)) - }).catch((error) => { - dispatch(resolveDirectoryError({ error })) - }) - return promise -} - -export const fetchProviderError = (error: any) => { - return { - type: 'FETCH_PROVIDER_ERROR', - payload: error - } -} - -export const fetchProviderRequest = (promise: Promise) => { - return { - type: 'FETCH_PROVIDER_REQUEST', - payload: promise - } -} - -export const fetchProviderSuccess = (provider: any) => { - return { - type: 'FETCH_PROVIDER_SUCCESS', - payload: provider - } -} - -export const fileAddedSuccess = (path: string, files) => { - return { - type: 'FILE_ADDED', - payload: { path, files } - } -} - -export const folderAddedSuccess = (path: string, files) => { - return { - type: 'FOLDER_ADDED', - payload: { path, files } - } -} - -export const fileRemovedSuccess = (path: string, removePath: string) => { - return { - type: 'FILE_REMOVED', - payload: { path, removePath } - } -} - -export const fileRenamedSuccess = (path: string, removePath: string, files) => { - return { - type: 'FILE_RENAMED', - payload: { path, removePath, files } - } -} - -export const init = (fileProvider, filePanel, registry) => (reducerDispatch: React.Dispatch) => { - provider = fileProvider - plugin = filePanel - dispatch = reducerDispatch - if (provider) { - provider.event.on('fileAdded', async (filePath) => { - await executeEvent('fileAdded', filePath) - }) - provider.event.on('folderAdded', async (folderPath) => { - await executeEvent('folderAdded', folderPath) - }) - provider.event.on('fileRemoved', async (removePath) => { - await executeEvent('fileRemoved', removePath) - }) - provider.event.on('fileRenamed', async (oldPath) => { - await executeEvent('fileRenamed', oldPath) - }) - provider.event.on('rootFolderChanged', async () => { - await executeEvent('rootFolderChanged') - }) - provider.event.on('fileExternallyChanged', async (path: string, file: { content: string }) => { - const config = registry.get('config').api - const editor = registry.get('editor').api - - if (config.get('currentFile') === path && editor.currentContent() !== file.content) { - if (provider.isReadOnly(path)) return editor.setText(file.content) - dispatch(displayNotification( - path + ' changed', - 'This file has been changed outside of Remix IDE.', - 'Replace by the new content', 'Keep the content displayed in Remix', - () => { - editor.setText(file.content) - } - )) - } - }) - provider.event.on('fileRenamedError', async () => { - dispatch(displayNotification('File Renamed Failed', '', 'Ok', 'Cancel')) - }) - dispatch(fetchProviderSuccess(provider)) - } else { - dispatch(fetchProviderError('No provider available')) - } -} - -export const setCurrentWorkspace = (name: string) => { - return { - type: 'SET_CURRENT_WORKSPACE', - payload: name - } -} - -export const addInputFieldSuccess = (path: string, files: File[]) => { - return { - type: 'ADD_INPUT_FIELD', - payload: { path, files } - } -} - -export const addInputField = (provider, type: string, path: string) => (dispatch: React.Dispatch) => { - const promise = fetchDirectoryContent(provider, path, type) - - promise.then((files) => { - dispatch(addInputFieldSuccess(path, files)) - }).catch((error) => { - console.error(error) - }) - return promise -} - -export const removeInputFieldSuccess = (path: string) => { - return { - type: 'REMOVE_INPUT_FIELD', - payload: { path } - } -} - -export const removeInputField = (path: string) => (dispatch: React.Dispatch) => { - return dispatch(removeInputFieldSuccess(path)) -} - -export const displayNotification = (title: string, message: string, labelOk: string, labelCancel: string, actionOk?: (...args) => void, actionCancel?: (...args) => void) => { - return { - type: 'DISPLAY_NOTIFICATION', - payload: { title, message, labelOk, labelCancel, actionOk, actionCancel } - } -} - -export const hideNotification = () => { - return { - type: 'DISPLAY_NOTIFICATION' - } -} - -export const closeNotificationModal = () => (dispatch: React.Dispatch) => { - dispatch(hideNotification()) -} - -const fileAdded = async (filePath: string) => { - if (extractParentFromKey(filePath) === '/.workspaces') return - const path = extractParentFromKey(filePath) || provider.workspace || provider.type || '' - const data = await fetchDirectoryContent(provider, path) - - await dispatch(fileAddedSuccess(path, data)) - if (filePath.includes('_test.sol')) { - plugin.emit('newTestFileCreated', filePath) - } -} - -const folderAdded = async (folderPath: string) => { - if (extractParentFromKey(folderPath) === '/.workspaces') return - const path = extractParentFromKey(folderPath) || provider.workspace || provider.type || '' - const data = await fetchDirectoryContent(provider, path) - - await dispatch(folderAddedSuccess(path, data)) -} - -const fileRemoved = async (removePath: string) => { - const path = extractParentFromKey(removePath) || provider.workspace || provider.type || '' - - await dispatch(fileRemovedSuccess(path, removePath)) -} - -const fileRenamed = async (oldPath: string) => { - const path = extractParentFromKey(oldPath) || provider.workspace || provider.type || '' - const data = await fetchDirectoryContent(provider, path) - - await dispatch(fileRenamedSuccess(path, oldPath, data)) -} - -const rootFolderChanged = async () => { - const workspaceName = provider.workspace || provider.type || '' - - await fetchDirectory(provider, workspaceName)(dispatch) -} - -const executeEvent = async (eventName: 'fileAdded' | 'folderAdded' | 'fileRemoved' | 'fileRenamed' | 'rootFolderChanged', path?: string) => { - if (Object.keys(pendingEvents).length) { - return queuedEvents.push({ eventName, path }) - } - pendingEvents[eventName + path] = { eventName, path } - switch (eventName) { - case 'fileAdded': - await fileAdded(path) - delete pendingEvents[eventName + path] - if (queuedEvents.length) { - const next = queuedEvents.pop() - - await executeEvent(next.eventName, next.path) - } - break - - case 'folderAdded': - await folderAdded(path) - delete pendingEvents[eventName + path] - if (queuedEvents.length) { - const next = queuedEvents.pop() - - await executeEvent(next.eventName, next.path) - } - break - - case 'fileRemoved': - await fileRemoved(path) - delete pendingEvents[eventName + path] - if (queuedEvents.length) { - const next = queuedEvents.pop() - - await executeEvent(next.eventName, next.path) - } - break - - case 'fileRenamed': - await fileRenamed(path) - delete pendingEvents[eventName + path] - if (queuedEvents.length) { - const next = queuedEvents.pop() - - await executeEvent(next.eventName, next.path) - } - break - - case 'rootFolderChanged': - await rootFolderChanged() - delete pendingEvents[eventName + path] - if (queuedEvents.length) { - const next = queuedEvents.pop() - - await executeEvent(next.eventName, next.path) - } - break - } -} diff --git a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx index b95b9c5c7e..47f4443364 100644 --- a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx +++ b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx @@ -11,9 +11,10 @@ import { customAction } from '@remixproject/plugin-api/lib/file-system/file-pane import { contextMenuActions } from './utils' import './css/file-explorer.css' +import { extractParentFromKey } from '@remix-ui/helper' export const FileExplorer = (props: FileExplorerProps) => { - const { name, focusRoot, contextMenuItems, displayInput, externalUploads, removedContextMenuItems, resetFocus, files } = props + const { name, focusRoot, contextMenuItems, externalUploads, removedContextMenuItems, resetFocus, files } = props const [state, setState] = useState({ focusElement: [{ key: '', @@ -62,7 +63,7 @@ export const FileExplorer = (props: FileExplorerProps) => { if (editRef && editRef.current) { editRef.current.focus() } - }, 150) + }, 0) } }, [state.focusEdit.element]) @@ -88,11 +89,12 @@ export const FileExplorer = (props: FileExplorerProps) => { }, [contextMenuItems]) useEffect(() => { - if (displayInput) { - handleNewFileInput() - plugin.resetNewFile() + if (global.fs.focusEdit) { + setState(prevState => { + return { ...prevState, focusEdit: { element: global.fs.focusEdit, type: 'file', isNew: true, lastEdit: null } } + }) } - }, [displayInput]) + }, [global.fs.focusEdit]) useEffect(() => { if (externalUploads) { @@ -173,14 +175,6 @@ export const FileExplorer = (props: FileExplorerProps) => { return keyPath[keyPath.length - 1] } - const extractParentFromKey = (key: string):string => { - if (!key) return - const keyPath = key.split('/') - keyPath.pop() - - return keyPath.join('/') - } - const hasReservedKeyword = (content: string): boolean => { if (state.reservedKeywords.findIndex(value => content.startsWith(value)) !== -1) return true else return false @@ -196,22 +190,11 @@ export const FileExplorer = (props: FileExplorerProps) => { } const createNewFile = async (newFilePath: string) => { - const fileManager = state.fileManager - try { - const newName = await helper.createNonClashingNameAsync(newFilePath, fileManager) - const createFile = await fileManager.writeFile(newName, '') - - if (!createFile) { - return global.toast('Failed to create file ' + newName) - } else { - const path = newName.indexOf(props.name + '/') === 0 ? newName.replace(props.name + '/', '') : newName - - await fileManager.open(path) - setState(prevState => { - return { ...prevState, focusElement: [{ key: newName, type: 'file' }] } - }) - } + global.dispatchCreateNewFile(newFilePath, props.name) + // setState(prevState => { + // return { ...prevState, focusElement: [{ key: newName, type: 'file' }] } + // }) } catch (error) { return global.modal('File Creation Failed', typeof error === 'string' ? error : error.message, 'Close', async () => {}) } @@ -536,12 +519,14 @@ export const FileExplorer = (props: FileExplorerProps) => { } const label = (file: File) => { + const isEditable = (state.focusEdit.element === file.path) || (global.fs.focusEdit === file.path) + return (
{ e.stopPropagation() diff --git a/libs/remix-ui/file-explorer/src/lib/reducers/fileSystem.ts b/libs/remix-ui/file-explorer/src/lib/reducers/fileSystem.ts deleted file mode 100644 index 7646e03036..0000000000 --- a/libs/remix-ui/file-explorer/src/lib/reducers/fileSystem.ts +++ /dev/null @@ -1,348 +0,0 @@ -import * as _ from 'lodash' -import { extractNameFromKey } from '../utils' -interface Action { - type: string; - payload: Record; -} - -export const fileSystemInitialState = { - files: { - files: [], - expandPath: [], - blankPath: null, - isRequesting: false, - isSuccessful: false, - error: null - }, - provider: { - provider: null, - isRequesting: false, - isSuccessful: false, - error: null - }, - notification: { - title: null, - message: null, - actionOk: () => {}, - actionCancel: () => {}, - labelOk: null, - labelCancel: null - } -} - -export const fileSystemReducer = (state = fileSystemInitialState, action: Action) => { - switch (action.type) { - case 'FETCH_DIRECTORY_REQUEST': { - return { - ...state, - files: { - ...state.files, - isRequesting: true, - isSuccessful: false, - error: null - } - } - } - case 'FETCH_DIRECTORY_SUCCESS': { - return { - ...state, - files: { - ...state.files, - files: action.payload.files, - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'FETCH_DIRECTORY_ERROR': { - return { - ...state, - files: { - ...state.files, - isRequesting: false, - isSuccessful: false, - error: action.payload - } - } - } - case 'RESOLVE_DIRECTORY_REQUEST': { - return { - ...state, - files: { - ...state.files, - isRequesting: true, - isSuccessful: false, - error: null - } - } - } - case 'RESOLVE_DIRECTORY_SUCCESS': { - return { - ...state, - files: { - ...state.files, - files: resolveDirectory(state.provider.provider, action.payload.path, state.files.files, action.payload.files), - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'RESOLVE_DIRECTORY_ERROR': { - return { - ...state, - files: { - ...state.files, - isRequesting: false, - isSuccessful: false, - error: action.payload - } - } - } - case 'FETCH_PROVIDER_REQUEST': { - return { - ...state, - provider: { - ...state.provider, - isRequesting: true, - isSuccessful: false, - error: null - } - } - } - case 'FETCH_PROVIDER_SUCCESS': { - return { - ...state, - provider: { - ...state.provider, - provider: action.payload, - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'FETCH_PROVIDER_ERROR': { - return { - ...state, - provider: { - ...state.provider, - isRequesting: false, - isSuccessful: false, - error: action.payload - } - } - } - case 'ADD_INPUT_FIELD': { - return { - ...state, - files: { - ...state.files, - files: addInputField(state.provider.provider, action.payload.path, state.files.files, action.payload.files), - blankPath: action.payload.path, - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'REMOVE_INPUT_FIELD': { - return { - ...state, - files: { - ...state.files, - files: removeInputField(state.provider.provider, state.files.blankPath, state.files.files), - blankPath: null, - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'FILE_ADDED': { - return { - ...state, - files: { - ...state.files, - files: fileAdded(state.provider.provider, action.payload.path, state.files.files, action.payload.files), - expandPath: [...new Set([...state.files.expandPath, action.payload.path])], - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'FOLDER_ADDED': { - return { - ...state, - files: { - ...state.files, - files: folderAdded(state.provider.provider, action.payload.path, state.files.files, action.payload.files), - expandPath: [...new Set([...state.files.expandPath, action.payload.path])], - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'FILE_REMOVED': { - return { - ...state, - files: { - ...state.files, - files: fileRemoved(state.provider.provider, action.payload.path, action.payload.removePath, state.files.files), - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'FILE_RENAMED': { - return { - ...state, - files: { - ...state.files, - files: fileRenamed(state.provider.provider, action.payload.path, action.payload.removePath, state.files.files, action.payload.files), - isRequesting: false, - isSuccessful: true, - error: null - } - } - } - case 'DISPLAY_NOTIFICATION': { - return { - ...state, - notification: { - title: action.payload.title, - message: action.payload.message, - actionOk: action.payload.actionOk || fileSystemInitialState.notification.actionOk, - actionCancel: action.payload.actionCancel || fileSystemInitialState.notification.actionCancel, - labelOk: action.payload.labelOk, - labelCancel: action.payload.labelCancel - } - } - } - case 'HIDE_NOTIFICATION': { - return { - ...state, - notification: fileSystemInitialState.notification - } - } - default: - throw new Error() - } -} - -const resolveDirectory = (provider, path: string, files, content) => { - const root = provider.workspace || provider.type - - if (path === root) return { [root]: { ...content[root], ...files[root] } } - const pathArr: string[] = path.split('/').filter(value => value) - - if (pathArr[0] !== root) pathArr.unshift(root) - const _path = pathArr.map((key, index) => index > 1 ? ['child', key] : key).reduce((acc: string[], cur) => { - return Array.isArray(cur) ? [...acc, ...cur] : [...acc, cur] - }, []) - - const prevFiles = _.get(files, _path) - - files = _.set(files, _path, { - isDirectory: true, - path, - name: extractNameFromKey(path).indexOf('gist-') === 0 ? extractNameFromKey(path).split('-')[1] : extractNameFromKey(path), - type: extractNameFromKey(path).indexOf('gist-') === 0 ? 'gist' : 'folder', - child: { ...content[pathArr[pathArr.length - 1]], ...(prevFiles ? prevFiles.child : {}) } - }) - - return files -} - -const removePath = (root, path: string, pathName, files) => { - const pathArr: string[] = path.split('/').filter(value => value) - - if (pathArr[0] !== root) pathArr.unshift(root) - const _path = pathArr.map((key, index) => index > 1 ? ['child', key] : key).reduce((acc: string[], cur) => { - return Array.isArray(cur) ? [...acc, ...cur] : [...acc, cur] - }, []) - const prevFiles = _.get(files, _path) - if (prevFiles) { - prevFiles.child && prevFiles.child[pathName] && delete prevFiles.child[pathName] - files = _.set(files, _path, { - isDirectory: true, - path, - name: extractNameFromKey(path).indexOf('gist-') === 0 ? extractNameFromKey(path).split('-')[1] : extractNameFromKey(path), - type: extractNameFromKey(path).indexOf('gist-') === 0 ? 'gist' : 'folder', - child: prevFiles ? prevFiles.child : {} - }) - } - - return files -} - -const addInputField = (provider, path: string, files, content) => { - const root = provider.workspace || provider.type || '' - - if (path === root) return { [root]: { ...content[root], ...files[root] } } - const result = resolveDirectory(provider, path, files, content) - - return result -} - -const removeInputField = (provider, path: string, files) => { - const root = provider.workspace || provider.type || '' - - if (path === root) { - delete files[root][path + '/' + 'blank'] - return files - } - return removePath(root, path, path + '/' + 'blank', files) -} - -const fileAdded = (provider, path: string, files, content) => { - return resolveDirectory(provider, path, files, content) -} - -const folderAdded = (provider, path: string, files, content) => { - return resolveDirectory(provider, path, files, content) -} - -const fileRemoved = (provider, path: string, removedPath: string, files) => { - const root = provider.workspace || provider.type || '' - - if (path === root) { - delete files[root][removedPath] - - return files - } - return removePath(root, path, extractNameFromKey(removedPath), files) -} - -const fileRenamed = (provider, path: string, removePath: string, files, content) => { - const root = provider.workspace || provider.type || '' - - if (path === root) { - const allFiles = { [root]: { ...content[root], ...files[root] } } - - delete allFiles[root][extractNameFromKey(removePath) || removePath] - return allFiles - } - const pathArr: string[] = path.split('/').filter(value => value) - - if (pathArr[0] !== root) pathArr.unshift(root) - const _path = pathArr.map((key, index) => index > 1 ? ['child', key] : key).reduce((acc: string[], cur) => { - return Array.isArray(cur) ? [...acc, ...cur] : [...acc, cur] - }, []) - const prevFiles = _.get(files, _path) - - delete prevFiles.child[extractNameFromKey(removePath)] - files = _.set(files, _path, { - isDirectory: true, - path, - name: extractNameFromKey(path).indexOf('gist-') === 0 ? extractNameFromKey(path).split('-')[1] : extractNameFromKey(path), - type: extractNameFromKey(path).indexOf('gist-') === 0 ? 'gist' : 'folder', - child: { ...content[pathArr[pathArr.length - 1]], ...prevFiles.child } - }) - - return files -} diff --git a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts index fa7e98f49f..9ab07aa0c4 100644 --- a/libs/remix-ui/helper/src/lib/remix-ui-helper.ts +++ b/libs/remix-ui/helper/src/lib/remix-ui-helper.ts @@ -19,3 +19,25 @@ export const checkSpecialChars = (name: string) => { export const checkSlash = (name: string) => { return name.match(/\//) != null } + +export const createNonClashingNameAsync = async (name, fileManager, prefix = '') => { + if (!name) name = 'Undefined' + let counter + let ext = 'sol' + const reg = /(.*)\.([^.]+)/g + const split = reg.exec(name) + if (split) { + name = split[1] + ext = split[2] + } + let exist = true + + do { + const isDuplicate = await fileManager.exists(name + counter + prefix + '.' + ext) + + if (isDuplicate) counter = (counter | 0) + 1 + else exist = false + } while (exist) + + return name + counter + prefix + '.' + ext +} diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index 491b0d8e85..2cb874aad4 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -1,7 +1,7 @@ import React from 'react' import { bufferToHex, keccakFromString } from 'ethereumjs-util' import axios, { AxiosResponse } from 'axios' -import { checkSpecialChars, checkSlash, extractParentFromKey, extractNameFromKey } from '@remix-ui/helper' +import { checkSpecialChars, checkSlash, extractParentFromKey, extractNameFromKey, createNonClashingNameAsync } from '@remix-ui/helper' import Gists from 'gists' const QueryParams = require('../../../../../../apps/remix-ide/src/lib/query-params') @@ -421,7 +421,6 @@ const listenOnEvents = (provider) => { }) provider.event.on('fileRenamed', async (oldPath: string, newPath: string) => { - console.log('oldPath: ', oldPath, 'newPath: ', newPath) await executeEvent('fileRenamed', oldPath, newPath) }) @@ -469,9 +468,14 @@ const listenOnEvents = (provider) => { )) } }) + provider.event.on('fileRenamedError', async () => { dispatch(displayNotification('File Renamed Failed', '', 'Ok', 'Cancel')) }) + + plugin.on('filePanel', 'displayNewFileInput', (path) => { + addInputField('file', path)(dispatch) + }) } export const initWorkspace = (filePanelPlugin) => async (reducerDispatch: React.Dispatch) => { @@ -750,6 +754,20 @@ export const uploadFile = (target, targetFolder: string) => async (dispatch: Rea }) } +export const createNewFile = (path: string, rootDir: string) => async (dispatch: React.Dispatch) => { + const fileManager = plugin.fileManager + const newName = await createNonClashingNameAsync(path, fileManager) + const createFile = await fileManager.writeFile(newName, '') + + if (!createFile) { + return dispatch(displayPopUp('Failed to create file ' + newName)) + } else { + const path = newName.indexOf(rootDir + '/') === 0 ? newName.replace(rootDir + '/', '') : newName + + await fileManager.open(path) + } +} + const fileAdded = async (filePath: string) => { await dispatch(fileAddedSuccess(filePath)) if (filePath.includes('_test.sol')) { diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts index eed19039c9..e7a9960433 100644 --- a/libs/remix-ui/workspace/src/lib/contexts/index.ts +++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts @@ -15,5 +15,6 @@ export const FileSystemContext = createContext<{ dispatchRenameWorkspace: (oldName: string, workspaceName: string) => Promise, dispatchDeleteWorkspace: (workspaceName: string) => Promise, dispatchPublishToGist: (path?: string, type?: string) => Promise, - dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise + dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise, + dispatchCreateNewFile: (path: string, rootDir: string) => Promise }>(null) diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx index 66d0a3cc82..c5e63c0b68 100644 --- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx +++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx @@ -5,7 +5,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line // eslint-disable-next-line @typescript-eslint/no-unused-vars import { FileSystemContext } from '../contexts' import { browserReducer, browserInitialState } from '../reducers/workspace' -import { initWorkspace, fetchDirectory, addInputField, removeInputField, createWorkspace, fetchWorkspaceDirectory, switchToWorkspace, renameWorkspace, deleteWorkspace, clearPopUp, publishToGist, uploadFile } from '../actions/workspace' +import { initWorkspace, fetchDirectory, addInputField, removeInputField, createWorkspace, fetchWorkspaceDirectory, switchToWorkspace, renameWorkspace, deleteWorkspace, clearPopUp, publishToGist, uploadFile, createNewFile } from '../actions/workspace' import { Modal, WorkspaceProps } from '../types' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { Workspace } from '../remix-ui-workspace' @@ -70,6 +70,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => { await uploadFile(target, targetFolder)(fsDispatch) } + const dispatchCreateNewFile = async (path: string, rootDir: string) => { + await createNewFile(path, rootDir)(fsDispatch) + } + useEffect(() => { if (modals.length > 0) { setFocusModal(() => { @@ -154,7 +158,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => { dispatchRenameWorkspace, dispatchDeleteWorkspace, dispatchPublishToGist, - dispatchUploadFile + dispatchUploadFile, + dispatchCreateNewFile } return ( diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index acf87a2afc..c52700ca35 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -32,7 +32,8 @@ export interface BrowserState { labelCancel: string }, readonly: boolean, - popup: string + popup: string, + focusEdit: string } export const browserInitialState: BrowserState = { @@ -63,7 +64,8 @@ export const browserInitialState: BrowserState = { labelCancel: '' }, readonly: false, - popup: '' + popup: '', + focusEdit: '' } export const browserReducer = (state = browserInitialState, action: Action) => { @@ -320,7 +322,8 @@ export const browserReducer = (state = browserInitialState, action: Action) => { localhost: { ...state.localhost, files: state.mode === 'localhost' ? fetchDirectoryContent(state, payload) : state.localhost.files - } + }, + focusEdit: payload.path + '/' + 'blank' } } @@ -336,7 +339,8 @@ export const browserReducer = (state = browserInitialState, action: Action) => { localhost: { ...state.localhost, files: state.mode === 'localhost' ? fetchDirectoryContent(state, payload, payload.path + '/' + 'blank') : state.localhost.files - } + }, + focusEdit: null } }