Merge branch 'master' into festyless

pull/5370/head
Liana Husikyan 1 year ago committed by GitHub
commit 0144081038
  1. 44
      apps/remix-ide/src/app/files/fileManager.ts
  2. 3
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  3. 23
      libs/remix-ui/drag-n-drop/src/lib/drag-n-drop.tsx
  4. 13
      libs/remix-ui/workspace/src/lib/actions/index.ts
  5. 65
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  6. 17
      libs/remix-ui/workspace/src/lib/components/file-render.tsx
  7. 20
      libs/remix-ui/workspace/src/lib/utils/index.ts

@ -904,6 +904,50 @@ class FileManager extends Plugin {
return exists return exists
} }
async moveFileIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsFile(src, `Cannot move ${src}. Path is not a file.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const fileName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + fileName)) {
return false
}
return true
} catch (e) {
console.log(e)
return false
}
}
async moveDirIsAllowed (src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsDir(src, `Cannot move ${src}. Path is not directory.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const dirName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + dirName) || src === dest) {
return false
}
return true
} catch (e) {
console.log(e)
return false
}
}
/** /**
* Moves a file to a new folder * Moves a file to a new folder
* @param {string} src path of the source file * @param {string} src path of the source file

@ -73,6 +73,7 @@
"filePanel.features": "Features", "filePanel.features": "Features",
"filePanel.upgradeability": "Upgradeability", "filePanel.upgradeability": "Upgradeability",
"filePanel.ok": "OK", "filePanel.ok": "OK",
"filePanel.yes": "Yes",
"filePanel.cancel": "Cancel", "filePanel.cancel": "Cancel",
"filePanel.createNewWorkspace": "create a new workspace", "filePanel.createNewWorkspace": "create a new workspace",
"filePanel.connectToLocalhost": "connect to localhost", "filePanel.connectToLocalhost": "connect to localhost",
@ -115,6 +116,8 @@
"filePanel.validationErrorMsg": "Special characters are not allowed", "filePanel.validationErrorMsg": "Special characters are not allowed",
"filePanel.reservedKeyword": "Reserved Keyword", "filePanel.reservedKeyword": "Reserved Keyword",
"filePanel.reservedKeywordMsg": "File name contains Remix reserved keywords. \"{content}\"", "filePanel.reservedKeywordMsg": "File name contains Remix reserved keywords. \"{content}\"",
"filePanel.moveFile": "Moving files",
"filePanel.moveFileMsg1": "Are you sure you want to move {src} to {dest}?",
"filePanel.movingFileFailed": "Moving File Failed", "filePanel.movingFileFailed": "Moving File Failed",
"filePanel.movingFileFailedMsg": "Unexpected error while moving file: {src}", "filePanel.movingFileFailedMsg": "Unexpected error while moving file: {src}",
"filePanel.movingFolderFailed": "Moving Folder Failed", "filePanel.movingFolderFailed": "Moving Folder Failed",

@ -27,6 +27,11 @@ export const Draggable = (props: DraggableType) => {
destination = props.file, destination = props.file,
context = useContext(MoveContext) context = useContext(MoveContext)
// delay timer
const [timer, setTimer] = useState<NodeJS.Timeout>()
// folder to open
const [folderToOpen, setFolderToOpen] = useState<string>()
const handleDrop = (event: React.DragEvent<HTMLSpanElement>) => { const handleDrop = (event: React.DragEvent<HTMLSpanElement>) => {
event.preventDefault() event.preventDefault()
@ -50,8 +55,15 @@ export const Draggable = (props: DraggableType) => {
const handleDragover = (event: React.DragEvent<HTMLSpanElement>) => { const handleDragover = (event: React.DragEvent<HTMLSpanElement>) => {
//Checks if the folder is opened //Checks if the folder is opened
event.preventDefault() event.preventDefault()
if (destination.isDirectory && !props.expandedPath.includes(destination.path)) { if (destination.isDirectory && !props.expandedPath.includes(destination.path) && folderToOpen !== destination.path && props.handleClickFolder) {
props.handleClickFolder(destination.path, destination.type) setFolderToOpen(destination.path)
timer && clearTimeout(timer)
setTimer(
setTimeout(() => {
props.handleClickFolder(destination.path, destination.type)
setFolderToOpen(null)
}, 600)
)
} }
} }
@ -75,7 +87,12 @@ export const Draggable = (props: DraggableType) => {
onDrop={(event) => { onDrop={(event) => {
handleDrop(event) handleDrop(event)
}} }}
onDragStart={() => { onDragStart={(event) => {
if (destination && destination.path === '/'){
event.preventDefault()
event.stopPropagation
} else
if (destination) { if (destination) {
handleDrag() handleDrag()
} }

@ -514,3 +514,16 @@ export const moveFolder = async (src: string, dest: string) => {
dispatch(displayPopUp('Oops! An error ocurred while performing moveDir operation.' + error)) dispatch(displayPopUp('Oops! An error ocurred while performing moveDir operation.' + error))
} }
} }
export const moveFileIsAllowed = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
const isAllowed = await fileManager.moveFileIsAllowed(src, dest)
return isAllowed
}
export const moveFolderIsAllowed = async (src: string, dest: string) => {
const fileManager = plugin.fileManager
const isAllowed = await fileManager.moveDirIsAllowed(src, dest)
return isAllowed
}

@ -2,14 +2,17 @@ import React, {useEffect, useState, useRef, SyntheticEvent} from 'react' // esli
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import {TreeView} from '@remix-ui/tree-view' // eslint-disable-line import {TreeView} from '@remix-ui/tree-view' // eslint-disable-line
import {FileExplorerMenu} from './file-explorer-menu' // eslint-disable-line import {FileExplorerMenu} from './file-explorer-menu' // eslint-disable-line
import {FileExplorerProps, WorkSpaceState} from '../types' import {FileExplorerContextMenu} from './file-explorer-context-menu' // eslint-disable-line
import {FileExplorerProps, FileType, WorkSpaceState} from '../types'
import '../css/file-explorer.css' import '../css/file-explorer.css'
import {checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath} from '@remix-ui/helper' import {checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath} from '@remix-ui/helper'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import {FileRender} from './file-render' import {FileRender} from './file-render'
import {Drag} from '@remix-ui/drag-n-drop' import {Drag, Draggable} from '@remix-ui/drag-n-drop'
import {ROOT_PATH} from '../utils/constants' import {ROOT_PATH} from '../utils/constants'
import { fileKeySort } from '../utils'
import { moveFileIsAllowed, moveFolderIsAllowed } from '../actions'
export const FileExplorer = (props: FileExplorerProps) => { export const FileExplorer = (props: FileExplorerProps) => {
const intl = useIntl() const intl = useIntl()
@ -31,6 +34,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
} = props } = props
const [state, setState] = useState<WorkSpaceState>(workspaceState) const [state, setState] = useState<WorkSpaceState>(workspaceState)
const treeRef = useRef<HTMLDivElement>(null) const treeRef = useRef<HTMLDivElement>(null)
const [childrenKeys, setChildrenKeys] = useState<string[]>([])
useEffect(() => { useEffect(() => {
if (contextMenuItems) { if (contextMenuItems) {
@ -288,32 +292,62 @@ export const FileExplorer = (props: FileExplorerProps) => {
props.dispatchHandleExpandPath(expandPath) props.dispatchHandleExpandPath(expandPath)
} }
const handleFileMove = (dest: string, src: string) => { const handleFileMove = async (dest: string, src: string) => {
if(await moveFileIsAllowed(src, dest) === false) return
try { try {
props.dispatchMoveFile(src, dest) props.modal(
intl.formatMessage({ id: 'filePanel.moveFile' }),
intl.formatMessage({ id: 'filePanel.moveFileMsg1' }, { src, dest }),
intl.formatMessage({ id: 'filePanel.yes' }),
() => props.dispatchMoveFile(src, dest),
intl.formatMessage({ id: 'filePanel.cancel' }),
() => {}
)
} catch (error) { } catch (error) {
props.modal( props.modal(
intl.formatMessage({id: 'filePanel.movingFileFailed'}), intl.formatMessage({ id: 'filePanel.movingFileFailed' }),
intl.formatMessage({id: 'filePanel.movingFileFailedMsg'}, {src}), intl.formatMessage({ id: 'filePanel.movingFileFailedMsg' }, { src }),
intl.formatMessage({id: 'filePanel.close'}), intl.formatMessage({ id: 'filePanel.close' }),
async () => {} async () => {}
) )
} }
} }
const handleFolderMove = (dest: string, src: string) => { const handleFolderMove = async (dest: string, src: string) => {
if(await moveFolderIsAllowed(src, dest) === false) return
try { try {
props.dispatchMoveFolder(src, dest) props.modal(
intl.formatMessage({ id: 'filePanel.moveFile' }),
intl.formatMessage({ id: 'filePanel.moveFileMsg1' }, { src, dest }),
intl.formatMessage({ id: 'filePanel.yes' }),
() => props.dispatchMoveFolder(src, dest),
intl.formatMessage({ id: 'filePanel.cancel' }),
() => {}
)
} catch (error) { } catch (error) {
props.modal( props.modal(
intl.formatMessage({id: 'filePanel.movingFolderFailed'}), intl.formatMessage({ id: 'filePanel.movingFolderFailed' }),
intl.formatMessage({id: 'filePanel.movingFolderFailedMsg'}, {src}), intl.formatMessage({ id: 'filePanel.movingFolderFailedMsg' }, { src }),
intl.formatMessage({id: 'filePanel.close'}), intl.formatMessage({ id: 'filePanel.close' }),
async () => {} async () => {}
) )
} }
} }
useEffect(() => {
if (files[ROOT_PATH]){
try {
const children: FileType[] = files[ROOT_PATH] as any
setChildrenKeys(fileKeySort(children))
} catch (error) {
setChildrenKeys(Object.keys(files[ROOT_PATH]))
}
} else{
setChildrenKeys([])
}
}, [props])
return ( return (
<Drag onFileMoved={handleFileMove} onFolderMoved={handleFolderMove}> <Drag onFileMoved={handleFileMove} onFolderMoved={handleFolderMove}>
<div ref={treeRef} tabIndex={0} style={{outline: 'none'}}> <div ref={treeRef} tabIndex={0} style={{outline: 'none'}}>
@ -339,10 +373,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
</span> </span>
</div> </div>
</li> </li>
<div className="pb-4 mb-4"> <div>
<TreeView id="treeViewMenu"> <TreeView id="treeViewMenu">
{files[ROOT_PATH] && {files[ROOT_PATH] &&
Object.keys(files[ROOT_PATH]).map((key, index) => ( childrenKeys.map((key, index) => (
<FileRender <FileRender
file={files[ROOT_PATH][key]} file={files[ROOT_PATH][key]}
fileDecorations={fileState} fileDecorations={fileState}
@ -364,6 +398,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
</TreeView> </TreeView>
</div> </div>
<Draggable isDraggable={false} file={{ name: '/', path: '/', type: 'folder', isDirectory: true }} expandedPath={props.expandPath} handleClickFolder={null}>
<div className='d-block w-100 pb-4 mb-4'></div>
</Draggable>
</TreeView> </TreeView>
</div> </div>
</Drag> </Drag>

@ -8,6 +8,7 @@ import {getPathIcon} from '@remix-ui/helper'
import {FileLabel} from './file-label' import {FileLabel} from './file-label'
import {fileDecoration, FileDecorationIcons} from '@remix-ui/file-decorators' import {fileDecoration, FileDecorationIcons} from '@remix-ui/file-decorators'
import {Draggable} from '@remix-ui/drag-n-drop' import {Draggable} from '@remix-ui/drag-n-drop'
import { fileKeySort } from '../utils'
export interface RenderFileProps { export interface RenderFileProps {
file: FileType file: FileType
@ -30,6 +31,7 @@ export const FileRender = (props: RenderFileProps) => {
const [file, setFile] = useState<FileType>({} as FileType) const [file, setFile] = useState<FileType>({} as FileType)
const [hover, setHover] = useState<boolean>(false) const [hover, setHover] = useState<boolean>(false)
const [icon, setIcon] = useState<string>('') const [icon, setIcon] = useState<string>('')
const [childrenKeys, setChildrenKeys] = useState<string[]>([])
useEffect(() => { useEffect(() => {
if (props.file && props.file.path && props.file.type) { if (props.file && props.file.path && props.file.type) {
@ -38,6 +40,19 @@ export const FileRender = (props: RenderFileProps) => {
} }
}, [props.file]) }, [props.file])
useEffect(() => {
if (file.child) {
try {
const children: FileType[] = file.child as any
setChildrenKeys(fileKeySort(children))
} catch (e) {
setChildrenKeys(Object.keys(file.child))
}
} else {
setChildrenKeys([])
}
}, [file.child, props.expandPath, props.file])
const labelClass = const labelClass =
props.focusEdit.element === file.path props.focusEdit.element === file.path
? 'bg-light' ? 'bg-light'
@ -108,7 +123,7 @@ export const FileRender = (props: RenderFileProps) => {
> >
{file.child ? ( {file.child ? (
<TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps}> <TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps}>
{Object.keys(file.child).map((key, index) => ( {childrenKeys.map((key, index) => (
<FileRender <FileRender
file={file.child[key]} file={file.child[key]}
fileDecorations={props.fileDecorations} fileDecorations={props.fileDecorations}

@ -1,3 +1,4 @@
import { FileType } from '@remix-ui/file-decorators'
import { WorkspaceProps, MenuItems } from '../types' import { WorkspaceProps, MenuItems } from '../types'
export const contextMenuActions: MenuItems = [{ export const contextMenuActions: MenuItems = [{
@ -113,4 +114,21 @@ export const contextMenuActions: MenuItems = [{
multiselect: false, multiselect: false,
label: '', label: '',
group: 4 group: 4
}] }]
export const fileKeySort = (children: FileType[]): string[] => {
const directories = Object.keys(children).filter((key: string) => children[key].isDirectory && children[key].name !== '')
// sort case insensitive
directories.sort((a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()))
const fileKeys = Object.keys(children).filter((key: string) => !children[key].isDirectory && children[key].name !== '')
// sort case insensitive
fileKeys.sort((a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()))
// find the children with a blank name
const blankChildren = Object.keys(children).filter((key: string) => children[key].name === '')
const keys = [...directories, ...fileKeys, ...blankChildren]
return keys
}
Loading…
Cancel
Save