Update file manager design a bit
This commit is contained in:
parent
8bd518048e
commit
2824db7352
9 changed files with 184 additions and 152 deletions
|
@ -61,7 +61,7 @@ return [
|
||||||
'copy' => 'Created a copy of :file',
|
'copy' => 'Created a copy of :file',
|
||||||
'create-directory' => 'Created a new directory :name in :directory',
|
'create-directory' => 'Created a new directory :name in :directory',
|
||||||
'decompress' => 'Decompressed :files in :directory',
|
'decompress' => 'Decompressed :files in :directory',
|
||||||
'delete_one' => 'Deleted :directory:files',
|
'delete_one' => 'Deleted :directory:files.0',
|
||||||
'delete_other' => 'Deleted :count files in :directory',
|
'delete_other' => 'Deleted :count files in :directory',
|
||||||
'download' => 'Downloaded :file',
|
'download' => 'Downloaded :file',
|
||||||
'pull' => 'Downloaded a remote file from :url to :directory',
|
'pull' => 'Downloaded a remote file from :url to :directory',
|
||||||
|
|
8
resources/scripts/components/elements/Portal.tsx
Normal file
8
resources/scripts/components/elements/Portal.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
export default ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const element = useRef(document.getElementById('modal-portal'));
|
||||||
|
|
||||||
|
return createPortal(children, element!.current!);
|
||||||
|
};
|
|
@ -44,8 +44,8 @@ const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }:
|
||||||
open={open}
|
open={open}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<div className={'fixed inset-0 bg-gray-900/50'}/>
|
<div className={'fixed inset-0 bg-gray-900/50 z-40'}/>
|
||||||
<div className={'fixed inset-0 overflow-y-auto'}>
|
<div className={'fixed inset-0 overflow-y-auto z-50'}>
|
||||||
<div className={'flex min-h-full items-center justify-center p-4 text-center'}>
|
<div className={'flex min-h-full items-center justify-center p-4 text-center'}>
|
||||||
<HDialog.Panel
|
<HDialog.Panel
|
||||||
as={motion.div}
|
as={motion.div}
|
||||||
|
@ -58,7 +58,7 @@ const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }:
|
||||||
'ring-4 ring-gray-800 ring-opacity-80',
|
'ring-4 ring-gray-800 ring-opacity-80',
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
<div className={'flex p-6'}>
|
<div className={'flex p-6 overflow-y-auto'}>
|
||||||
{icon && <div className={'mr-4'}>{icon}</div>}
|
{icon && <div className={'mr-4'}>{icon}</div>}
|
||||||
<div className={'flex-1 max-h-[70vh]'}>
|
<div className={'flex-1 max-h-[70vh]'}>
|
||||||
{title &&
|
{title &&
|
||||||
|
|
|
@ -30,8 +30,8 @@ import useEventListener from '@/plugins/useEventListener';
|
||||||
import compressFiles from '@/api/server/files/compressFiles';
|
import compressFiles from '@/api/server/files/compressFiles';
|
||||||
import decompressFiles from '@/api/server/files/decompressFiles';
|
import decompressFiles from '@/api/server/files/decompressFiles';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
|
||||||
import ChmodFileModal from '@/components/server/files/ChmodFileModal';
|
import ChmodFileModal from '@/components/server/files/ChmodFileModal';
|
||||||
|
import { Dialog } from '@/components/elements/dialog';
|
||||||
|
|
||||||
type ModalType = 'rename' | 'move' | 'chmod';
|
type ModalType = 'rename' | 'move' | 'chmod';
|
||||||
|
|
||||||
|
@ -128,15 +128,16 @@ const FileDropdownMenu = ({ file }: { file: FileObject }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConfirmationModal
|
<Dialog.Confirm
|
||||||
visible={showConfirmation}
|
open={showConfirmation}
|
||||||
title={`Delete this ${file.isFile ? 'File' : 'Directory'}?`}
|
onClose={() => setShowConfirmation(false)}
|
||||||
buttonText={`Yes, Delete ${file.isFile ? 'File' : 'Directory'}`}
|
title={`Delete ${file.isFile ? 'File' : 'Directory'}`}
|
||||||
|
confirm={'Delete'}
|
||||||
onConfirmed={doDeletion}
|
onConfirmed={doDeletion}
|
||||||
onModalDismissed={() => setShowConfirmation(false)}
|
|
||||||
>
|
>
|
||||||
Deleting files is a permanent operation, you cannot undo this action.
|
You will not be able to recover the contents of
|
||||||
</ConfirmationModal>
|
<span className={'font-semibold text-gray-50'}>{file.name}</span> once deleted.
|
||||||
|
</Dialog.Confirm>
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
ref={onClickRef}
|
ref={onClickRef}
|
||||||
renderToggle={onClick => (
|
renderToggle={onClick => (
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { NavLink, useLocation } from 'react-router-dom';
|
||||||
import Can from '@/components/elements/Can';
|
import Can from '@/components/elements/Can';
|
||||||
import { ServerError } from '@/components/elements/ScreenBlock';
|
import { ServerError } from '@/components/elements/ScreenBlock';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button/index';
|
||||||
import { ServerContext } from '@/state/server';
|
import { ServerContext } from '@/state/server';
|
||||||
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
||||||
import MassActionsBar from '@/components/server/files/MassActionsBar';
|
import MassActionsBar from '@/components/server/files/MassActionsBar';
|
||||||
|
@ -20,6 +20,7 @@ import { useStoreActions } from '@/state/hooks';
|
||||||
import ErrorBoundary from '@/components/elements/ErrorBoundary';
|
import ErrorBoundary from '@/components/elements/ErrorBoundary';
|
||||||
import { FileActionCheckbox } from '@/components/server/files/SelectFileCheckbox';
|
import { FileActionCheckbox } from '@/components/server/files/SelectFileCheckbox';
|
||||||
import { hashToPath } from '@/helpers';
|
import { hashToPath } from '@/helpers';
|
||||||
|
import style from './style.module.css';
|
||||||
|
|
||||||
const sortFiles = (files: FileObject[]): FileObject[] => {
|
const sortFiles = (files: FileObject[]): FileObject[] => {
|
||||||
const sortedFiles: FileObject[] = files.sort((a, b) => a.name.localeCompare(b.name)).sort((a, b) => a.isFile === b.isFile ? 0 : (a.isFile ? 1 : -1));
|
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 () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerContentBlock title={'File Manager'} showFlashKey={'files'}>
|
<ServerContentBlock title={'File Manager'} showFlashKey={'files'}>
|
||||||
<div css={tw`flex flex-wrap-reverse md:flex-nowrap justify-center mb-4`}>
|
<ErrorBoundary>
|
||||||
<ErrorBoundary>
|
<div className={'flex flex-wrap-reverse md:flex-nowrap mb-4'}>
|
||||||
<FileManagerBreadcrumbs
|
<FileManagerBreadcrumbs
|
||||||
renderLeft={
|
renderLeft={
|
||||||
<FileActionCheckbox
|
<FileActionCheckbox
|
||||||
|
@ -71,24 +72,17 @@ export default () => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
<Can action={'file.create'}>
|
||||||
<Can action={'file.create'}>
|
<div className={style.manager_actions}>
|
||||||
<ErrorBoundary>
|
<NewDirectoryButton/>
|
||||||
<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`}>
|
<UploadButton/>
|
||||||
<NewDirectoryButton css={tw`w-full flex-none mt-4 sm:mt-0 sm:w-auto sm:mr-4`}/>
|
<NavLink to={`/server/${id}/files/new${window.location.hash}`}>
|
||||||
<UploadButton css={tw`flex-1 mr-4 sm:flex-none sm:mt-0`}/>
|
<Button>New File</Button>
|
||||||
<NavLink
|
|
||||||
to={`/server/${id}/files/new${window.location.hash}`}
|
|
||||||
css={tw`flex-1 sm:flex-none sm:mt-0`}
|
|
||||||
>
|
|
||||||
<Button css={tw`w-full`}>
|
|
||||||
New File
|
|
||||||
</Button>
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</Can>
|
||||||
</Can>
|
</div>
|
||||||
</div>
|
</ErrorBoundary>
|
||||||
{
|
{
|
||||||
!files ?
|
!files ?
|
||||||
<Spinner size={'large'} centered/>
|
<Spinner size={'large'} centered/>
|
||||||
|
@ -102,12 +96,12 @@ export default () => {
|
||||||
<CSSTransition classNames={'fade'} timeout={150} appear in>
|
<CSSTransition classNames={'fade'} timeout={150} appear in>
|
||||||
<div>
|
<div>
|
||||||
{files.length > 250 &&
|
{files.length > 250 &&
|
||||||
<div css={tw`rounded bg-yellow-400 mb-px p-3`}>
|
<div css={tw`rounded bg-yellow-400 mb-px p-3`}>
|
||||||
<p css={tw`text-yellow-900 text-sm text-center`}>
|
<p css={tw`text-yellow-900 text-sm text-center`}>
|
||||||
This directory is too large to display in the browser,
|
This directory is too large to display in the browser,
|
||||||
limiting the output to the first 250 files.
|
limiting the output to the first 250 files.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
sortFiles(files.slice(0, 250)).map(file => (
|
sortFiles(files.slice(0, 250)).map(file => (
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button/index';
|
||||||
import Fade from '@/components/elements/Fade';
|
import Fade from '@/components/elements/Fade';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faFileArchive, faLevelUpAlt, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import compressFiles from '@/api/server/files/compressFiles';
|
import compressFiles from '@/api/server/files/compressFiles';
|
||||||
import { ServerContext } from '@/state/server';
|
import { ServerContext } from '@/state/server';
|
||||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
|
||||||
import deleteFiles from '@/api/server/files/deleteFiles';
|
import deleteFiles from '@/api/server/files/deleteFiles';
|
||||||
import RenameFileModal from '@/components/server/files/RenameFileModal';
|
import RenameFileModal from '@/components/server/files/RenameFileModal';
|
||||||
|
import Portal from '@/components/elements/Portal';
|
||||||
|
import { Dialog } from '@/components/elements/dialog';
|
||||||
|
|
||||||
const MassActionsBar = () => {
|
const MassActionsBar = () => {
|
||||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||||
|
@ -62,53 +61,54 @@ const MassActionsBar = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fade timeout={75} in={selectedFiles.length > 0} unmountOnExit>
|
<>
|
||||||
<div css={tw`pointer-events-none fixed bottom-0 z-20 left-0 right-0 flex justify-center`}>
|
<div css={tw`pointer-events-none fixed bottom-0 z-20 left-0 right-0 flex justify-center`}>
|
||||||
<SpinnerOverlay visible={loading} size={'large'} fixed>
|
<SpinnerOverlay visible={loading} size={'large'} fixed>
|
||||||
{loadingMessage}
|
{loadingMessage}
|
||||||
</SpinnerOverlay>
|
</SpinnerOverlay>
|
||||||
<ConfirmationModal
|
<Dialog.Confirm
|
||||||
visible={showConfirm}
|
title={'Delete Files'}
|
||||||
title={'Delete these files?'}
|
open={showConfirm}
|
||||||
buttonText={'Yes, Delete Files'}
|
confirm={'Delete'}
|
||||||
|
onClose={() => setShowConfirm(false)}
|
||||||
onConfirmed={onClickConfirmDeletion}
|
onConfirmed={onClickConfirmDeletion}
|
||||||
onModalDismissed={() => setShowConfirm(false)}
|
|
||||||
>
|
>
|
||||||
Are you sure you want to delete {selectedFiles.length} file(s)?
|
<p className={'mb-2'}>
|
||||||
<br/>
|
Are you sure you want to delete
|
||||||
Deleting the file(s) listed below is a permanent operation, you cannot undo this action.
|
<span className={'font-semibold text-gray-50'}>{selectedFiles.length} files</span>? This is
|
||||||
<br/>
|
a permanent action and the files cannot be recovered.
|
||||||
<code>
|
</p>
|
||||||
{ selectedFiles.slice(0, 15).map(file => (
|
{selectedFiles.slice(0, 15).map(file => (
|
||||||
<li key={file}>{file}<br/></li>))
|
<li key={file}>{file}</li>))
|
||||||
}
|
}
|
||||||
{ selectedFiles.length > 15 &&
|
{selectedFiles.length > 15 &&
|
||||||
<li> + {selectedFiles.length - 15} other(s) </li>
|
<li>and {selectedFiles.length - 15} others</li>
|
||||||
}
|
}
|
||||||
</code>
|
</Dialog.Confirm>
|
||||||
</ConfirmationModal>
|
|
||||||
{showMove &&
|
{showMove &&
|
||||||
<RenameFileModal
|
<RenameFileModal
|
||||||
files={selectedFiles}
|
files={selectedFiles}
|
||||||
visible
|
visible
|
||||||
appear
|
appear
|
||||||
useMoveTerminology
|
useMoveTerminology
|
||||||
onDismissed={() => setShowMove(false)}
|
onDismissed={() => setShowMove(false)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<div css={tw`pointer-events-auto rounded p-4 mb-6`} style={{ background: 'rgba(0, 0, 0, 0.35)' }}>
|
<Portal>
|
||||||
<Button size={'xsmall'} css={tw`mr-4`} onClick={() => setShowMove(true)}>
|
<div className={'fixed bottom-0 mb-6 flex justify-center w-full z-50'}>
|
||||||
<FontAwesomeIcon icon={faLevelUpAlt} css={tw`mr-2`}/> Move
|
<Fade timeout={75} in={selectedFiles.length > 0} unmountOnExit>
|
||||||
</Button>
|
<div css={tw`flex items-center space-x-4 pointer-events-auto rounded p-4 bg-black/50`}>
|
||||||
<Button size={'xsmall'} css={tw`mr-4`} onClick={onClickCompress}>
|
<Button onClick={() => setShowMove(true)}>Move</Button>
|
||||||
<FontAwesomeIcon icon={faFileArchive} css={tw`mr-2`}/> Archive
|
<Button onClick={onClickCompress}>Archive</Button>
|
||||||
</Button>
|
<Button.Danger variant={Button.Variants.Secondary} onClick={() => setShowConfirm(true)}>
|
||||||
<Button size={'xsmall'} color={'red'} isSecondary onClick={() => setShowConfirm(true)}>
|
Delete
|
||||||
<FontAwesomeIcon icon={faTrashAlt} css={tw`mr-2`}/> Delete
|
</Button.Danger>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</Fade>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
</div>
|
</div>
|
||||||
</Fade>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Modal from '@/components/elements/Modal';
|
|
||||||
import { ServerContext } from '@/state/server';
|
import { ServerContext } from '@/state/server';
|
||||||
import { Form, Formik, FormikHelpers } from 'formik';
|
import { Form, Formik, FormikHelpers } from 'formik';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
|
@ -7,12 +6,15 @@ import { join } from 'path';
|
||||||
import { object, string } from 'yup';
|
import { object, string } from 'yup';
|
||||||
import createDirectory from '@/api/server/files/createDirectory';
|
import createDirectory from '@/api/server/files/createDirectory';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button/index';
|
||||||
import { FileObject } from '@/api/server/files/loadDirectory';
|
import { FileObject } from '@/api/server/files/loadDirectory';
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
||||||
import { WithClassname } from '@/components/types';
|
import { WithClassname } from '@/components/types';
|
||||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
|
import { Dialog } from '@/components/elements/dialog';
|
||||||
|
import Portal from '@/components/elements/Portal';
|
||||||
|
import Code from '@/components/elements/Code';
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
directoryName: string;
|
directoryName: string;
|
||||||
|
@ -66,48 +68,57 @@ export default ({ className }: WithClassname) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Formik
|
<Portal>
|
||||||
onSubmit={submit}
|
<Formik
|
||||||
validationSchema={schema}
|
onSubmit={submit}
|
||||||
initialValues={{ directoryName: '' }}
|
validationSchema={schema}
|
||||||
>
|
initialValues={{ directoryName: '' }}
|
||||||
{({ resetForm, isSubmitting, values }) => (
|
>
|
||||||
<Modal
|
{({ resetForm, submitForm, isSubmitting: _, values }) => (
|
||||||
visible={visible}
|
<Dialog
|
||||||
dismissable={!isSubmitting}
|
title={'Create Directory'}
|
||||||
showSpinnerOverlay={isSubmitting}
|
open={visible}
|
||||||
onDismissed={() => {
|
onClose={() => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
resetForm();
|
resetForm();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FlashMessageRender key={'files:directory-modal'}/>
|
<FlashMessageRender key={'files:directory-modal'}/>
|
||||||
<Form css={tw`m-0`}>
|
<Form css={tw`m-0`}>
|
||||||
<Field
|
<Field
|
||||||
autoFocus
|
autoFocus
|
||||||
id={'directoryName'}
|
id={'directoryName'}
|
||||||
name={'directoryName'}
|
name={'directoryName'}
|
||||||
label={'Directory Name'}
|
label={'Name'}
|
||||||
/>
|
/>
|
||||||
<p css={tw`text-xs mt-2 text-neutral-400 break-all`}>
|
<p css={tw`mt-2 text-sm md:text-base break-all`}>
|
||||||
<span css={tw`text-neutral-200`}>This directory will be created as</span>
|
<span css={tw`text-neutral-200`}>This directory will be created as </span>
|
||||||
/home/container/
|
<Code>/home/container/
|
||||||
<span css={tw`text-cyan-200`}>
|
<span css={tw`text-cyan-200`}>
|
||||||
{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}
|
{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</Code>
|
||||||
<div css={tw`flex justify-end`}>
|
</p>
|
||||||
<Button css={tw`mt-8`}>
|
</Form>
|
||||||
Create Directory
|
<Dialog.Buttons>
|
||||||
</Button>
|
<Button.Text
|
||||||
</div>
|
className={'w-full sm:w-auto'}
|
||||||
</Form>
|
onClick={() => {
|
||||||
</Modal>
|
setVisible(false);
|
||||||
)}
|
resetForm();
|
||||||
</Formik>
|
}}
|
||||||
<Button isSecondary onClick={() => setVisible(true)} className={className}>
|
>
|
||||||
|
Cancel
|
||||||
|
</Button.Text>
|
||||||
|
<Button className={'w-full sm:w-auto'} onClick={submitForm}>Create</Button>
|
||||||
|
</Dialog.Buttons>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Portal>
|
||||||
|
<Button.Text onClick={() => setVisible(true)} className={className}>
|
||||||
Create Directory
|
Create Directory
|
||||||
</Button>
|
</Button.Text>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import getFileUploadUrl from '@/api/server/files/getFileUploadUrl';
|
import getFileUploadUrl from '@/api/server/files/getFileUploadUrl';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
import Button from '@/components/elements/Button';
|
import { Button } from '@/components/elements/button/index';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
import { ModalMask } from '@/components/elements/Modal';
|
import { ModalMask } from '@/components/elements/Modal';
|
||||||
|
@ -12,6 +12,7 @@ import useFlash from '@/plugins/useFlash';
|
||||||
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
|
||||||
import { ServerContext } from '@/state/server';
|
import { ServerContext } from '@/state/server';
|
||||||
import { WithClassname } from '@/components/types';
|
import { WithClassname } from '@/components/types';
|
||||||
|
import Portal from '@/components/elements/Portal';
|
||||||
|
|
||||||
const InnerContainer = styled.div`
|
const InnerContainer = styled.div`
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
@ -71,36 +72,38 @@ export default ({ className }: WithClassname) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Fade
|
<Portal>
|
||||||
appear
|
<Fade
|
||||||
in={visible}
|
appear
|
||||||
timeout={75}
|
in={visible}
|
||||||
key={'upload_modal_mask'}
|
timeout={75}
|
||||||
unmountOnExit
|
key={'upload_modal_mask'}
|
||||||
>
|
unmountOnExit
|
||||||
<ModalMask
|
|
||||||
onClick={() => setVisible(false)}
|
|
||||||
onDragOver={e => e.preventDefault()}
|
|
||||||
onDrop={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
setVisible(false);
|
|
||||||
if (!e.dataTransfer?.files.length) return;
|
|
||||||
|
|
||||||
onFileSubmission(e.dataTransfer.files);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div css={tw`w-full flex items-center justify-center`} style={{ pointerEvents: 'none' }}>
|
<ModalMask
|
||||||
<InnerContainer>
|
onClick={() => setVisible(false)}
|
||||||
<p css={tw`text-lg text-neutral-200 text-center`}>
|
onDragOver={e => e.preventDefault()}
|
||||||
Drag and drop files to upload.
|
onDrop={e => {
|
||||||
</p>
|
e.preventDefault();
|
||||||
</InnerContainer>
|
e.stopPropagation();
|
||||||
</div>
|
|
||||||
</ModalMask>
|
setVisible(false);
|
||||||
</Fade>
|
if (!e.dataTransfer?.files.length) return;
|
||||||
<SpinnerOverlay visible={loading} size={'large'} fixed/>
|
|
||||||
|
onFileSubmission(e.dataTransfer.files);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div css={tw`w-full flex items-center justify-center`} style={{ pointerEvents: 'none' }}>
|
||||||
|
<InnerContainer>
|
||||||
|
<p css={tw`text-lg text-neutral-200 text-center`}>
|
||||||
|
Drag and drop files to upload.
|
||||||
|
</p>
|
||||||
|
</InnerContainer>
|
||||||
|
</div>
|
||||||
|
</ModalMask>
|
||||||
|
</Fade>
|
||||||
|
<SpinnerOverlay visible={loading} size={'large'} fixed/>
|
||||||
|
</Portal>
|
||||||
<input
|
<input
|
||||||
type={'file'}
|
type={'file'}
|
||||||
ref={fileUploadInput}
|
ref={fileUploadInput}
|
||||||
|
|
15
resources/scripts/components/server/files/style.module.css
Normal file
15
resources/scripts/components/server/files/style.module.css
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
.manager_actions {
|
||||||
|
@apply grid grid-cols-2 sm:grid-cols-3 w-full gap-4 mb-4;
|
||||||
|
|
||||||
|
& button {
|
||||||
|
@apply w-full first:col-span-2 sm:first:col-span-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen md {
|
||||||
|
@apply flex flex-1 justify-end mb-0;
|
||||||
|
|
||||||
|
& button {
|
||||||
|
@apply w-auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue