Skip to content

Commit 2824db7

Browse files
committed
Update file manager design a bit
1 parent 8bd5180 commit 2824db7

File tree

9 files changed

+183
-151
lines changed

9 files changed

+183
-151
lines changed

resources/lang/en/activity.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
'copy' => 'Created a copy of :file',
6262
'create-directory' => 'Created a new directory :name in :directory',
6363
'decompress' => 'Decompressed :files in :directory',
64-
'delete_one' => 'Deleted :directory:files',
64+
'delete_one' => 'Deleted :directory:files.0',
6565
'delete_other' => 'Deleted :count files in :directory',
6666
'download' => 'Downloaded :file',
6767
'pull' => 'Downloaded a remote file from :url to :directory',
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React, { useRef } from 'react';
2+
import { createPortal } from 'react-dom';
3+
4+
export default ({ children }: { children: React.ReactNode }) => {
5+
const element = useRef(document.getElementById('modal-portal'));
6+
7+
return createPortal(children, element!.current!);
8+
};

resources/scripts/components/elements/dialog/Dialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }:
4444
open={open}
4545
onClose={onClose}
4646
>
47-
<div className={'fixed inset-0 bg-gray-900/50'}/>
48-
<div className={'fixed inset-0 overflow-y-auto'}>
47+
<div className={'fixed inset-0 bg-gray-900/50 z-40'}/>
48+
<div className={'fixed inset-0 overflow-y-auto z-50'}>
4949
<div className={'flex min-h-full items-center justify-center p-4 text-center'}>
5050
<HDialog.Panel
5151
as={motion.div}
@@ -58,7 +58,7 @@ const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }:
5858
'ring-4 ring-gray-800 ring-opacity-80',
5959
])}
6060
>
61-
<div className={'flex p-6'}>
61+
<div className={'flex p-6 overflow-y-auto'}>
6262
{icon && <div className={'mr-4'}>{icon}</div>}
6363
<div className={'flex-1 max-h-[70vh]'}>
6464
{title &&

resources/scripts/components/server/files/FileDropdownMenu.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import useEventListener from '@/plugins/useEventListener';
3030
import compressFiles from '@/api/server/files/compressFiles';
3131
import decompressFiles from '@/api/server/files/decompressFiles';
3232
import isEqual from 'react-fast-compare';
33-
import ConfirmationModal from '@/components/elements/ConfirmationModal';
3433
import ChmodFileModal from '@/components/server/files/ChmodFileModal';
34+
import { Dialog } from '@/components/elements/dialog';
3535

3636
type ModalType = 'rename' | 'move' | 'chmod';
3737

@@ -128,15 +128,16 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => {
128128

129129
return (
130130
<>
131-
<ConfirmationModal
132-
visible={showConfirmation}
133-
title={`Delete this ${file.isFile ? 'File' : 'Directory'}?`}
134-
buttonText={`Yes, Delete ${file.isFile ? 'File' : 'Directory'}`}
131+
<Dialog.Confirm
132+
open={showConfirmation}
133+
onClose={() => setShowConfirmation(false)}
134+
title={`Delete ${file.isFile ? 'File' : 'Directory'}`}
135+
confirm={'Delete'}
135136
onConfirmed={doDeletion}
136-
onModalDismissed={() => setShowConfirmation(false)}
137137
>
138-
Deleting files is a permanent operation, you cannot undo this action.
139-
</ConfirmationModal>
138+
You will not be able to recover the contents of&nbsp;
139+
<span className={'font-semibold text-gray-50'}>{file.name}</span> once deleted.
140+
</Dialog.Confirm>
140141
<DropdownMenu
141142
ref={onClickRef}
142143
renderToggle={onClick => (

resources/scripts/components/server/files/FileManagerContainer.tsx

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { NavLink, useLocation } from 'react-router-dom';
1010
import Can from '@/components/elements/Can';
1111
import { ServerError } from '@/components/elements/ScreenBlock';
1212
import tw from 'twin.macro';
13-
import Button from '@/components/elements/Button';
13+
import { Button } from '@/components/elements/button/index';
1414
import { ServerContext } from '@/state/server';
1515
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
1616
import MassActionsBar from '@/components/server/files/MassActionsBar';
@@ -20,6 +20,7 @@ import { useStoreActions } from '@/state/hooks';
2020
import ErrorBoundary from '@/components/elements/ErrorBoundary';
2121
import { FileActionCheckbox } from '@/components/server/files/SelectFileCheckbox';
2222
import { hashToPath } from '@/helpers';
23+
import style from './style.module.css';
2324

2425
const sortFiles = (files: FileObject[]): FileObject[] => {
2526
const sortedFiles: FileObject[] = files.sort((a, b) => a.name.localeCompare(b.name)).sort((a, b) => a.isFile === b.isFile ? 0 : (a.isFile ? 1 : -1));
@@ -59,8 +60,8 @@ export default () => {
5960

6061
return (
6162
<ServerContentBlock title={'File Manager'} showFlashKey={'files'}>
62-
<div css={tw`flex flex-wrap-reverse md:flex-nowrap justify-center mb-4`}>
63-
<ErrorBoundary>
63+
<ErrorBoundary>
64+
<div className={'flex flex-wrap-reverse md:flex-nowrap mb-4'}>
6465
<FileManagerBreadcrumbs
6566
renderLeft={
6667
<FileActionCheckbox
@@ -71,24 +72,17 @@ export default () => {
7172
/>
7273
}
7374
/>
74-
</ErrorBoundary>
75-
<Can action={'file.create'}>
76-
<ErrorBoundary>
77-
<div css={tw`flex flex-shrink-0 flex-wrap-reverse md:flex-nowrap justify-end mb-4 md:mb-0 ml-0 md:ml-auto`}>
78-
<NewDirectoryButton css={tw`w-full flex-none mt-4 sm:mt-0 sm:w-auto sm:mr-4`}/>
79-
<UploadButton css={tw`flex-1 mr-4 sm:flex-none sm:mt-0`}/>
80-
<NavLink
81-
to={`/server/${id}/files/new${window.location.hash}`}
82-
css={tw`flex-1 sm:flex-none sm:mt-0`}
83-
>
84-
<Button css={tw`w-full`}>
85-
New File
86-
</Button>
75+
<Can action={'file.create'}>
76+
<div className={style.manager_actions}>
77+
<NewDirectoryButton/>
78+
<UploadButton/>
79+
<NavLink to={`/server/${id}/files/new${window.location.hash}`}>
80+
<Button>New File</Button>
8781
</NavLink>
8882
</div>
89-
</ErrorBoundary>
90-
</Can>
91-
</div>
83+
</Can>
84+
</div>
85+
</ErrorBoundary>
9286
{
9387
!files ?
9488
<Spinner size={'large'} centered/>
@@ -102,12 +96,12 @@ export default () => {
10296
<CSSTransition classNames={'fade'} timeout={150} appear in>
10397
<div>
10498
{files.length > 250 &&
105-
<div css={tw`rounded bg-yellow-400 mb-px p-3`}>
106-
<p css={tw`text-yellow-900 text-sm text-center`}>
107-
This directory is too large to display in the browser,
108-
limiting the output to the first 250 files.
109-
</p>
110-
</div>
99+
<div css={tw`rounded bg-yellow-400 mb-px p-3`}>
100+
<p css={tw`text-yellow-900 text-sm text-center`}>
101+
This directory is too large to display in the browser,
102+
limiting the output to the first 250 files.
103+
</p>
104+
</div>
111105
}
112106
{
113107
sortFiles(files.slice(0, 250)).map(file => (

resources/scripts/components/server/files/MassActionsBar.tsx

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import React, { useEffect, useState } from 'react';
22
import tw from 'twin.macro';
3-
import Button from '@/components/elements/Button';
3+
import { Button } from '@/components/elements/button/index';
44
import Fade from '@/components/elements/Fade';
5-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6-
import { faFileArchive, faLevelUpAlt, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
75
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
86
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
97
import useFlash from '@/plugins/useFlash';
108
import compressFiles from '@/api/server/files/compressFiles';
119
import { ServerContext } from '@/state/server';
12-
import ConfirmationModal from '@/components/elements/ConfirmationModal';
1310
import deleteFiles from '@/api/server/files/deleteFiles';
1411
import RenameFileModal from '@/components/server/files/RenameFileModal';
12+
import Portal from '@/components/elements/Portal';
13+
import { Dialog } from '@/components/elements/dialog';
1514

1615
const MassActionsBar = () => {
1716
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
@@ -62,53 +61,54 @@ const MassActionsBar = () => {
6261
};
6362

6463
return (
65-
<Fade timeout={75} in={selectedFiles.length > 0} unmountOnExit>
64+
<>
6665
<div css={tw`pointer-events-none fixed bottom-0 z-20 left-0 right-0 flex justify-center`}>
6766
<SpinnerOverlay visible={loading} size={'large'} fixed>
6867
{loadingMessage}
6968
</SpinnerOverlay>
70-
<ConfirmationModal
71-
visible={showConfirm}
72-
title={'Delete these files?'}
73-
buttonText={'Yes, Delete Files'}
69+
<Dialog.Confirm
70+
title={'Delete Files'}
71+
open={showConfirm}
72+
confirm={'Delete'}
73+
onClose={() => setShowConfirm(false)}
7474
onConfirmed={onClickConfirmDeletion}
75-
onModalDismissed={() => setShowConfirm(false)}
7675
>
77-
Are you sure you want to delete {selectedFiles.length} file(s)?
78-
<br/>
79-
Deleting the file(s) listed below is a permanent operation, you cannot undo this action.
80-
<br/>
81-
<code>
82-
{ selectedFiles.slice(0, 15).map(file => (
83-
<li key={file}>{file}<br/></li>))
84-
}
85-
{ selectedFiles.length > 15 &&
86-
<li> + {selectedFiles.length - 15} other(s) </li>
87-
}
88-
</code>
89-
</ConfirmationModal>
76+
<p className={'mb-2'}>
77+
Are you sure you want to delete&nbsp;
78+
<span className={'font-semibold text-gray-50'}>{selectedFiles.length} files</span>? This is
79+
a permanent action and the files cannot be recovered.
80+
</p>
81+
{selectedFiles.slice(0, 15).map(file => (
82+
<li key={file}>{file}</li>))
83+
}
84+
{selectedFiles.length > 15 &&
85+
<li>and {selectedFiles.length - 15} others</li>
86+
}
87+
</Dialog.Confirm>
9088
{showMove &&
91-
<RenameFileModal
92-
files={selectedFiles}
93-
visible
94-
appear
95-
useMoveTerminology
96-
onDismissed={() => setShowMove(false)}
97-
/>
89+
<RenameFileModal
90+
files={selectedFiles}
91+
visible
92+
appear
93+
useMoveTerminology
94+
onDismissed={() => setShowMove(false)}
95+
/>
9896
}
99-
<div css={tw`pointer-events-auto rounded p-4 mb-6`} style={{ background: 'rgba(0, 0, 0, 0.35)' }}>
100-
<Button size={'xsmall'} css={tw`mr-4`} onClick={() => setShowMove(true)}>
101-
<FontAwesomeIcon icon={faLevelUpAlt} css={tw`mr-2`}/> Move
102-
</Button>
103-
<Button size={'xsmall'} css={tw`mr-4`} onClick={onClickCompress}>
104-
<FontAwesomeIcon icon={faFileArchive} css={tw`mr-2`}/> Archive
105-
</Button>
106-
<Button size={'xsmall'} color={'red'} isSecondary onClick={() => setShowConfirm(true)}>
107-
<FontAwesomeIcon icon={faTrashAlt} css={tw`mr-2`}/> Delete
108-
</Button>
109-
</div>
97+
<Portal>
98+
<div className={'fixed bottom-0 mb-6 flex justify-center w-full z-50'}>
99+
<Fade timeout={75} in={selectedFiles.length > 0} unmountOnExit>
100+
<div css={tw`flex items-center space-x-4 pointer-events-auto rounded p-4 bg-black/50`}>
101+
<Button onClick={() => setShowMove(true)}>Move</Button>
102+
<Button onClick={onClickCompress}>Archive</Button>
103+
<Button.Danger variant={Button.Variants.Secondary} onClick={() => setShowConfirm(true)}>
104+
Delete
105+
</Button.Danger>
106+
</div>
107+
</Fade>
108+
</div>
109+
</Portal>
110110
</div>
111-
</Fade>
111+
</>
112112
);
113113
};
114114

resources/scripts/components/server/files/NewDirectoryButton.tsx

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import React, { useEffect, useState } from 'react';
2-
import Modal from '@/components/elements/Modal';
32
import { ServerContext } from '@/state/server';
43
import { Form, Formik, FormikHelpers } from 'formik';
54
import Field from '@/components/elements/Field';
65
import { join } from 'path';
76
import { object, string } from 'yup';
87
import createDirectory from '@/api/server/files/createDirectory';
98
import tw from 'twin.macro';
10-
import Button from '@/components/elements/Button';
9+
import { Button } from '@/components/elements/button/index';
1110
import { FileObject } from '@/api/server/files/loadDirectory';
1211
import useFlash from '@/plugins/useFlash';
1312
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
1413
import { WithClassname } from '@/components/types';
1514
import FlashMessageRender from '@/components/FlashMessageRender';
15+
import { Dialog } from '@/components/elements/dialog';
16+
import Portal from '@/components/elements/Portal';
17+
import Code from '@/components/elements/Code';
1618

1719
interface Values {
1820
directoryName: string;
@@ -66,48 +68,57 @@ export default ({ className }: WithClassname) => {
6668

6769
return (
6870
<>
69-
<Formik
70-
onSubmit={submit}
71-
validationSchema={schema}
72-
initialValues={{ directoryName: '' }}
73-
>
74-
{({ resetForm, isSubmitting, values }) => (
75-
<Modal
76-
visible={visible}
77-
dismissable={!isSubmitting}
78-
showSpinnerOverlay={isSubmitting}
79-
onDismissed={() => {
80-
setVisible(false);
81-
resetForm();
82-
}}
83-
>
84-
<FlashMessageRender key={'files:directory-modal'}/>
85-
<Form css={tw`m-0`}>
86-
<Field
87-
autoFocus
88-
id={'directoryName'}
89-
name={'directoryName'}
90-
label={'Directory Name'}
91-
/>
92-
<p css={tw`text-xs mt-2 text-neutral-400 break-all`}>
93-
<span css={tw`text-neutral-200`}>This directory will be created as</span>
94-
&nbsp;/home/container/
95-
<span css={tw`text-cyan-200`}>
96-
{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}
97-
</span>
98-
</p>
99-
<div css={tw`flex justify-end`}>
100-
<Button css={tw`mt-8`}>
101-
Create Directory
102-
</Button>
103-
</div>
104-
</Form>
105-
</Modal>
106-
)}
107-
</Formik>
108-
<Button isSecondary onClick={() => setVisible(true)} className={className}>
71+
<Portal>
72+
<Formik
73+
onSubmit={submit}
74+
validationSchema={schema}
75+
initialValues={{ directoryName: '' }}
76+
>
77+
{({ resetForm, submitForm, isSubmitting: _, values }) => (
78+
<Dialog
79+
title={'Create Directory'}
80+
open={visible}
81+
onClose={() => {
82+
setVisible(false);
83+
resetForm();
84+
}}
85+
>
86+
<FlashMessageRender key={'files:directory-modal'}/>
87+
<Form css={tw`m-0`}>
88+
<Field
89+
autoFocus
90+
id={'directoryName'}
91+
name={'directoryName'}
92+
label={'Name'}
93+
/>
94+
<p css={tw`mt-2 text-sm md:text-base break-all`}>
95+
<span css={tw`text-neutral-200`}>This directory will be created as&nbsp;</span>
96+
<Code>/home/container/
97+
<span css={tw`text-cyan-200`}>
98+
{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}
99+
</span>
100+
</Code>
101+
</p>
102+
</Form>
103+
<Dialog.Buttons>
104+
<Button.Text
105+
className={'w-full sm:w-auto'}
106+
onClick={() => {
107+
setVisible(false);
108+
resetForm();
109+
}}
110+
>
111+
Cancel
112+
</Button.Text>
113+
<Button className={'w-full sm:w-auto'} onClick={submitForm}>Create</Button>
114+
</Dialog.Buttons>
115+
</Dialog>
116+
)}
117+
</Formik>
118+
</Portal>
119+
<Button.Text onClick={() => setVisible(true)} className={className}>
109120
Create Directory
110-
</Button>
121+
</Button.Text>
111122
</>
112123
);
113124
};

0 commit comments

Comments
 (0)