Skip to content

Commit 3e4a320

Browse files
authored
Merge branch 'master' into festyless
2 parents c516f93 + fba5856 commit 3e4a320

File tree

7 files changed

+166
-19
lines changed

7 files changed

+166
-19
lines changed

apps/remix-ide/src/app/files/fileManager.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,50 @@ class FileManager extends Plugin {
904904
return exists
905905
}
906906

907+
908+
async moveFileIsAllowed (src: string, dest: string) {
909+
try {
910+
src = this.normalize(src)
911+
dest = this.normalize(dest)
912+
src = this.limitPluginScope(src)
913+
dest = this.limitPluginScope(dest)
914+
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
915+
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
916+
await this._handleIsFile(src, `Cannot move ${src}. Path is not a file.`)
917+
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
918+
const fileName = helper.extractNameFromKey(src)
919+
920+
if (await this.exists(dest + '/' + fileName)) {
921+
return false
922+
}
923+
return true
924+
} catch (e) {
925+
console.log(e)
926+
return false
927+
}
928+
}
929+
930+
async moveDirIsAllowed (src: string, dest: string) {
931+
try {
932+
src = this.normalize(src)
933+
dest = this.normalize(dest)
934+
src = this.limitPluginScope(src)
935+
dest = this.limitPluginScope(dest)
936+
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
937+
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
938+
await this._handleIsDir(src, `Cannot move ${src}. Path is not directory.`)
939+
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
940+
const dirName = helper.extractNameFromKey(src)
941+
if (await this.exists(dest + '/' + dirName) || src === dest) {
942+
return false
943+
}
944+
return true
945+
} catch (e) {
946+
console.log(e)
947+
return false
948+
}
949+
}
950+
907951
/**
908952
* Moves a file to a new folder
909953
* @param {string} src path of the source file

apps/remix-ide/src/app/tabs/locales/en/filePanel.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"filePanel.features": "Features",
7474
"filePanel.upgradeability": "Upgradeability",
7575
"filePanel.ok": "OK",
76+
"filePanel.yes": "Yes",
7677
"filePanel.cancel": "Cancel",
7778
"filePanel.createNewWorkspace": "create a new workspace",
7879
"filePanel.connectToLocalhost": "connect to localhost",
@@ -115,6 +116,8 @@
115116
"filePanel.validationErrorMsg": "Special characters are not allowed",
116117
"filePanel.reservedKeyword": "Reserved Keyword",
117118
"filePanel.reservedKeywordMsg": "File name contains Remix reserved keywords. \"{content}\"",
119+
"filePanel.moveFile": "Moving files",
120+
"filePanel.moveFileMsg1": "Are you sure you want to move {src} to {dest}?",
118121
"filePanel.movingFileFailed": "Moving File Failed",
119122
"filePanel.movingFileFailedMsg": "Unexpected error while moving file: {src}",
120123
"filePanel.movingFolderFailed": "Moving Folder Failed",

libs/remix-ui/drag-n-drop/src/lib/drag-n-drop.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ export const Draggable = (props: DraggableType) => {
2727
destination = props.file,
2828
context = useContext(MoveContext)
2929

30+
// delay timer
31+
const [timer, setTimer] = useState<NodeJS.Timeout>()
32+
// folder to open
33+
const [folderToOpen, setFolderToOpen] = useState<string>()
34+
3035
const handleDrop = (event: React.DragEvent<HTMLSpanElement>) => {
3136
event.preventDefault()
3237

@@ -50,8 +55,15 @@ export const Draggable = (props: DraggableType) => {
5055
const handleDragover = (event: React.DragEvent<HTMLSpanElement>) => {
5156
//Checks if the folder is opened
5257
event.preventDefault()
53-
if (destination.isDirectory && !props.expandedPath.includes(destination.path)) {
54-
props.handleClickFolder(destination.path, destination.type)
58+
if (destination.isDirectory && !props.expandedPath.includes(destination.path) && folderToOpen !== destination.path && props.handleClickFolder) {
59+
setFolderToOpen(destination.path)
60+
timer && clearTimeout(timer)
61+
setTimer(
62+
setTimeout(() => {
63+
props.handleClickFolder(destination.path, destination.type)
64+
setFolderToOpen(null)
65+
}, 600)
66+
)
5567
}
5668
}
5769

@@ -75,7 +87,12 @@ export const Draggable = (props: DraggableType) => {
7587
onDrop={(event) => {
7688
handleDrop(event)
7789
}}
78-
onDragStart={() => {
90+
onDragStart={(event) => {
91+
if (destination && destination.path === '/'){
92+
event.preventDefault()
93+
event.stopPropagation
94+
} else
95+
7996
if (destination) {
8097
handleDrag()
8198
}

libs/remix-ui/workspace/src/lib/actions/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,16 @@ export const moveFolder = async (src: string, dest: string) => {
514514
dispatch(displayPopUp('Oops! An error ocurred while performing moveDir operation.' + error))
515515
}
516516
}
517+
518+
export const moveFileIsAllowed = async (src: string, dest: string) => {
519+
const fileManager = plugin.fileManager
520+
const isAllowed = await fileManager.moveFileIsAllowed(src, dest)
521+
return isAllowed
522+
}
523+
524+
export const moveFolderIsAllowed = async (src: string, dest: string) => {
525+
const fileManager = plugin.fileManager
526+
const isAllowed = await fileManager.moveDirIsAllowed(src, dest)
527+
return isAllowed
528+
}
529+

libs/remix-ui/workspace/src/lib/components/file-explorer.tsx

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import React, {useEffect, useState, useRef, SyntheticEvent} from 'react' // esli
22
import {useIntl} from 'react-intl'
33
import {TreeView} from '@remix-ui/tree-view' // eslint-disable-line
44
import {FileExplorerMenu} from './file-explorer-menu' // eslint-disable-line
5-
import {FileExplorerProps, WorkSpaceState} from '../types'
5+
import {FileExplorerContextMenu} from './file-explorer-context-menu' // eslint-disable-line
6+
import {FileExplorerProps, FileType, WorkSpaceState} from '../types'
67

78
import '../css/file-explorer.css'
89
import {checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath} from '@remix-ui/helper'
910
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1011
import {FileRender} from './file-render'
11-
import {Drag} from '@remix-ui/drag-n-drop'
12+
import {Drag, Draggable} from '@remix-ui/drag-n-drop'
1213
import {ROOT_PATH} from '../utils/constants'
14+
import { fileKeySort } from '../utils'
15+
import { moveFileIsAllowed, moveFolderIsAllowed } from '../actions'
1316

1417
export const FileExplorer = (props: FileExplorerProps) => {
1518
const intl = useIntl()
@@ -31,6 +34,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
3134
} = props
3235
const [state, setState] = useState<WorkSpaceState>(workspaceState)
3336
const treeRef = useRef<HTMLDivElement>(null)
37+
const [childrenKeys, setChildrenKeys] = useState<string[]>([])
3438

3539
useEffect(() => {
3640
if (contextMenuItems) {
@@ -288,32 +292,62 @@ export const FileExplorer = (props: FileExplorerProps) => {
288292
props.dispatchHandleExpandPath(expandPath)
289293
}
290294

291-
const handleFileMove = (dest: string, src: string) => {
295+
const handleFileMove = async (dest: string, src: string) => {
296+
if(await moveFileIsAllowed(src, dest) === false) return
292297
try {
293-
props.dispatchMoveFile(src, dest)
298+
props.modal(
299+
intl.formatMessage({ id: 'filePanel.moveFile' }),
300+
intl.formatMessage({ id: 'filePanel.moveFileMsg1' }, { src, dest }),
301+
intl.formatMessage({ id: 'filePanel.yes' }),
302+
() => props.dispatchMoveFile(src, dest),
303+
intl.formatMessage({ id: 'filePanel.cancel' }),
304+
() => {}
305+
)
294306
} catch (error) {
295307
props.modal(
296-
intl.formatMessage({id: 'filePanel.movingFileFailed'}),
297-
intl.formatMessage({id: 'filePanel.movingFileFailedMsg'}, {src}),
298-
intl.formatMessage({id: 'filePanel.close'}),
308+
intl.formatMessage({ id: 'filePanel.movingFileFailed' }),
309+
intl.formatMessage({ id: 'filePanel.movingFileFailedMsg' }, { src }),
310+
intl.formatMessage({ id: 'filePanel.close' }),
299311
async () => {}
300312
)
301313
}
302314
}
303315

304-
const handleFolderMove = (dest: string, src: string) => {
316+
const handleFolderMove = async (dest: string, src: string) => {
317+
if(await moveFolderIsAllowed(src, dest) === false) return
305318
try {
306-
props.dispatchMoveFolder(src, dest)
319+
props.modal(
320+
intl.formatMessage({ id: 'filePanel.moveFile' }),
321+
intl.formatMessage({ id: 'filePanel.moveFileMsg1' }, { src, dest }),
322+
intl.formatMessage({ id: 'filePanel.yes' }),
323+
() => props.dispatchMoveFolder(src, dest),
324+
intl.formatMessage({ id: 'filePanel.cancel' }),
325+
() => {}
326+
)
307327
} catch (error) {
308328
props.modal(
309-
intl.formatMessage({id: 'filePanel.movingFolderFailed'}),
310-
intl.formatMessage({id: 'filePanel.movingFolderFailedMsg'}, {src}),
311-
intl.formatMessage({id: 'filePanel.close'}),
329+
intl.formatMessage({ id: 'filePanel.movingFolderFailed' }),
330+
intl.formatMessage({ id: 'filePanel.movingFolderFailedMsg' }, { src }),
331+
intl.formatMessage({ id: 'filePanel.close' }),
312332
async () => {}
313333
)
314334
}
315335
}
316336

337+
useEffect(() => {
338+
if (files[ROOT_PATH]){
339+
try {
340+
const children: FileType[] = files[ROOT_PATH] as any
341+
setChildrenKeys(fileKeySort(children))
342+
} catch (error) {
343+
setChildrenKeys(Object.keys(files[ROOT_PATH]))
344+
}
345+
} else{
346+
setChildrenKeys([])
347+
}
348+
}, [props])
349+
350+
317351
return (
318352
<Drag onFileMoved={handleFileMove} onFolderMoved={handleFolderMove}>
319353
<div ref={treeRef} tabIndex={0} style={{outline: 'none'}}>
@@ -339,10 +373,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
339373
</span>
340374
</div>
341375
</li>
342-
<div className="pb-4 mb-4">
376+
<div>
343377
<TreeView id="treeViewMenu">
344378
{files[ROOT_PATH] &&
345-
Object.keys(files[ROOT_PATH]).map((key, index) => (
379+
childrenKeys.map((key, index) => (
346380
<FileRender
347381
file={files[ROOT_PATH][key]}
348382
fileDecorations={fileState}
@@ -364,6 +398,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
364398
}
365399
</TreeView>
366400
</div>
401+
<Draggable isDraggable={false} file={{ name: '/', path: '/', type: 'folder', isDirectory: true }} expandedPath={props.expandPath} handleClickFolder={null}>
402+
<div className='d-block w-100 pb-4 mb-4'></div>
403+
</Draggable>
367404
</TreeView>
368405
</div>
369406
</Drag>

libs/remix-ui/workspace/src/lib/components/file-render.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {getPathIcon} from '@remix-ui/helper'
88
import {FileLabel} from './file-label'
99
import {fileDecoration, FileDecorationIcons} from '@remix-ui/file-decorators'
1010
import {Draggable} from '@remix-ui/drag-n-drop'
11+
import { fileKeySort } from '../utils'
1112

1213
export interface RenderFileProps {
1314
file: FileType
@@ -30,6 +31,7 @@ export const FileRender = (props: RenderFileProps) => {
3031
const [file, setFile] = useState<FileType>({} as FileType)
3132
const [hover, setHover] = useState<boolean>(false)
3233
const [icon, setIcon] = useState<string>('')
34+
const [childrenKeys, setChildrenKeys] = useState<string[]>([])
3335

3436
useEffect(() => {
3537
if (props.file && props.file.path && props.file.type) {
@@ -38,6 +40,19 @@ export const FileRender = (props: RenderFileProps) => {
3840
}
3941
}, [props.file])
4042

43+
useEffect(() => {
44+
if (file.child) {
45+
try {
46+
const children: FileType[] = file.child as any
47+
setChildrenKeys(fileKeySort(children))
48+
} catch (e) {
49+
setChildrenKeys(Object.keys(file.child))
50+
}
51+
} else {
52+
setChildrenKeys([])
53+
}
54+
}, [file.child, props.expandPath, props.file])
55+
4156
const labelClass =
4257
props.focusEdit.element === file.path
4358
? 'bg-light'
@@ -108,7 +123,7 @@ export const FileRender = (props: RenderFileProps) => {
108123
>
109124
{file.child ? (
110125
<TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps}>
111-
{Object.keys(file.child).map((key, index) => (
126+
{childrenKeys.map((key, index) => (
112127
<FileRender
113128
file={file.child[key]}
114129
fileDecorations={props.fileDecorations}

libs/remix-ui/workspace/src/lib/utils/index.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FileType } from '@remix-ui/file-decorators'
12
import { WorkspaceProps, MenuItems } from '../types'
23

34
export const contextMenuActions: MenuItems = [{
@@ -113,4 +114,21 @@ export const contextMenuActions: MenuItems = [{
113114
multiselect: false,
114115
label: '',
115116
group: 4
116-
}]
117+
}]
118+
119+
export const fileKeySort = (children: FileType[]): string[] => {
120+
const directories = Object.keys(children).filter((key: string) => children[key].isDirectory && children[key].name !== '')
121+
122+
// sort case insensitive
123+
directories.sort((a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()))
124+
125+
const fileKeys = Object.keys(children).filter((key: string) => !children[key].isDirectory && children[key].name !== '')
126+
// sort case insensitive
127+
fileKeys.sort((a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase()))
128+
129+
// find the children with a blank name
130+
const blankChildren = Object.keys(children).filter((key: string) => children[key].name === '')
131+
132+
const keys = [...directories, ...fileKeys, ...blankChildren]
133+
return keys
134+
}

0 commit comments

Comments
 (0)