forked from Alex/Pterodactyl-Panel
Fix up file manager
This commit is contained in:
parent
7e8a5f1271
commit
43fbefbdb6
@ -1,9 +1,8 @@
|
||||
import React, { useCallback, useEffect, useState, lazy } from 'react';
|
||||
import useRouter from 'use-react-router';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import ace, { Editor } from 'brace';
|
||||
import getFileContents from '@/api/server/files/getFileContents';
|
||||
import styled from 'styled-components/macro';
|
||||
import tw from 'twin.macro';
|
||||
import Select from '@/components/elements/Select';
|
||||
|
||||
// @ts-ignore
|
||||
require('brace/ext/modelist');
|
||||
@ -11,7 +10,7 @@ require('ayu-ace/mirage');
|
||||
|
||||
const EditorContainer = styled.div`
|
||||
min-height: 16rem;
|
||||
height: calc(100vh - 16rem);
|
||||
height: calc(100vh - 20rem);
|
||||
${tw`relative`};
|
||||
|
||||
#editor {
|
||||
@ -20,9 +19,7 @@ const EditorContainer = styled.div`
|
||||
`;
|
||||
|
||||
const modes: { [k: string]: string } = {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
assembly_x86: 'Assembly (x86)',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
c_cpp: 'C++',
|
||||
coffee: 'Coffeescript',
|
||||
css: 'CSS',
|
||||
@ -40,7 +37,6 @@ const modes: { [k: string]: string } = {
|
||||
properties: 'Properties',
|
||||
python: 'Python',
|
||||
ruby: 'Ruby',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
plain_text: 'Plaintext',
|
||||
toml: 'TOML',
|
||||
typescript: 'Typescript',
|
||||
@ -70,7 +66,7 @@ export default ({ style, initialContent, initialModePath, fetchContent, onConten
|
||||
|
||||
useEffect(() => {
|
||||
editor && editor.session.setMode(mode);
|
||||
}, [editor, mode]);
|
||||
}, [ editor, mode ]);
|
||||
|
||||
useEffect(() => {
|
||||
editor && editor.session.setValue(initialContent || '');
|
||||
@ -113,10 +109,9 @@ export default ({ style, initialContent, initialModePath, fetchContent, onConten
|
||||
return (
|
||||
<EditorContainer style={style}>
|
||||
<div id={'editor'} ref={ref}/>
|
||||
<div className={'absolute right-0 bottom-0 z-50'}>
|
||||
<div className={'m-3 rounded bg-neutral-900 border border-black'}>
|
||||
<select
|
||||
className={'input-dark'}
|
||||
<div css={tw`absolute right-0 bottom-0 z-50`}>
|
||||
<div css={tw`m-3 rounded bg-neutral-900 border border-black`}>
|
||||
<Select
|
||||
value={mode.split('/').pop()}
|
||||
onChange={e => setMode(`ace/mode/${e.currentTarget.value}`)}
|
||||
>
|
||||
@ -125,7 +120,7 @@ export default ({ style, initialContent, initialModePath, fetchContent, onConten
|
||||
<option key={key} value={key}>{modes[key]}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</EditorContainer>
|
||||
|
@ -69,8 +69,7 @@ const DropdownMenu = ({ renderToggle, children }: Props) => {
|
||||
e.stopPropagation();
|
||||
setVisible(false);
|
||||
}}
|
||||
css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500`}
|
||||
style={{ minWidth: '12rem' }}
|
||||
css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -18,6 +18,8 @@ import Can from '@/components/elements/Can';
|
||||
import getFileDownloadUrl from '@/api/server/files/getFileDownloadUrl';
|
||||
import useServer from '@/plugins/useServer';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import tw from 'twin.macro';
|
||||
import Fade from '@/components/elements/Fade';
|
||||
|
||||
type ModalType = 'rename' | 'move';
|
||||
|
||||
@ -113,7 +115,7 @@ export default ({ uuid }: { uuid: string }) => {
|
||||
<div key={`dropdown:${file.uuid}`}>
|
||||
<div
|
||||
ref={menuButton}
|
||||
className={'p-3 hover:text-white'}
|
||||
css={tw`p-3 hover:text-white`}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
if (!menuVisible) {
|
||||
@ -133,60 +135,60 @@ export default ({ uuid }: { uuid: string }) => {
|
||||
setMenuVisible(false);
|
||||
}}
|
||||
/>
|
||||
<SpinnerOverlay visible={showSpinner} fixed={true} size={'large'}/>
|
||||
<SpinnerOverlay visible={showSpinner} fixed size={'large'}/>
|
||||
</div>
|
||||
<CSSTransition timeout={250} in={menuVisible} unmountOnExit={true} classNames={'fade'}>
|
||||
<Fade timeout={250} in={menuVisible} unmountOnExit classNames={'fade'}>
|
||||
<div
|
||||
ref={menu}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setMenuVisible(false);
|
||||
}}
|
||||
className={'absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48'}
|
||||
css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48`}
|
||||
>
|
||||
<Can action={'file.update'}>
|
||||
<div
|
||||
onClick={() => setModal('rename')}
|
||||
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
||||
css={tw`hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencilAlt} className={'text-xs'}/>
|
||||
<span className={'ml-2'}>Rename</span>
|
||||
<FontAwesomeIcon icon={faPencilAlt} css={tw`text-xs`}/>
|
||||
<span css={tw`ml-2`}>Rename</span>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setModal('move')}
|
||||
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
||||
css={tw`hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faLevelUpAlt} className={'text-xs'}/>
|
||||
<span className={'ml-2'}>Move</span>
|
||||
<FontAwesomeIcon icon={faLevelUpAlt} css={tw`text-xs`}/>
|
||||
<span css={tw`ml-2`}>Move</span>
|
||||
</div>
|
||||
</Can>
|
||||
<Can action={'file.create'}>
|
||||
<div
|
||||
onClick={() => doCopy()}
|
||||
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
||||
css={tw`hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCopy} className={'text-xs'}/>
|
||||
<span className={'ml-2'}>Copy</span>
|
||||
<FontAwesomeIcon icon={faCopy} css={tw`text-xs`}/>
|
||||
<span css={tw`ml-2`}>Copy</span>
|
||||
</div>
|
||||
</Can>
|
||||
<div
|
||||
className={'hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded'}
|
||||
css={tw`hover:text-neutral-700 p-2 flex items-center hover:bg-neutral-100 rounded`}
|
||||
onClick={() => doDownload()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faFileDownload} className={'text-xs'}/>
|
||||
<span className={'ml-2'}>Download</span>
|
||||
<FontAwesomeIcon icon={faFileDownload} css={tw`text-xs`}/>
|
||||
<span css={tw`ml-2`}>Download</span>
|
||||
</div>
|
||||
<Can action={'file.delete'}>
|
||||
<div
|
||||
onClick={() => doDeletion()}
|
||||
className={'hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded'}
|
||||
css={tw`hover:text-red-700 p-2 flex items-center hover:bg-red-100 rounded`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt} className={'text-xs'}/>
|
||||
<span className={'ml-2'}>Delete</span>
|
||||
<FontAwesomeIcon icon={faTrashAlt} css={tw`text-xs`}/>
|
||||
<span css={tw`ml-2`}>Delete</span>
|
||||
</div>
|
||||
</Can>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</Fade>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -14,6 +14,8 @@ import Can from '@/components/elements/Can';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||
import ServerError from '@/components/screens/ServerError';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor'));
|
||||
|
||||
@ -81,16 +83,17 @@ export default () => {
|
||||
|
||||
return (
|
||||
<PageContentBlock>
|
||||
<FlashMessageRender byKey={'files:view'} className={'mb-4'}/>
|
||||
<FileManagerBreadcrumbs withinFileEditor={true} isNewFile={action !== 'edit'}/>
|
||||
{(name || hash.replace(/^#/, '')).endsWith('.pteroignore') &&
|
||||
<div className={'mb-4 p-4 border-l-4 bg-neutral-900 rounded border-cyan-400'}>
|
||||
<p className={'text-neutral-300 text-sm'}>
|
||||
You're editing a <code className={'font-mono bg-black rounded py-px px-1'}>.pteroignore</code> file.
|
||||
<FlashMessageRender byKey={'files:view'} css={tw`mb-4`}/>
|
||||
<FileManagerBreadcrumbs withinFileEditor isNewFile={action !== 'edit'}/>
|
||||
{hash.replace(/^#/, '').endsWith('.pteroignore') &&
|
||||
<div css={tw`mb-4 p-4 border-l-4 bg-neutral-900 rounded border-cyan-400`}>
|
||||
<p css={tw`text-neutral-300 text-sm`}>
|
||||
You're editing
|
||||
a <code css={tw`font-mono bg-black rounded py-px px-1`}>.pteroignore</code> file.
|
||||
Any files or directories listed in here will be excluded from backups. Wildcards are supported by
|
||||
using an asterisk (<code className={'font-mono bg-black rounded py-px px-1'}>*</code>). You can
|
||||
using an asterisk (<code css={tw`font-mono bg-black rounded py-px px-1`}>*</code>). You can
|
||||
negate a prior rule by prepending an exclamation point
|
||||
(<code className={'font-mono bg-black rounded py-px px-1'}>!</code>).
|
||||
(<code css={tw`font-mono bg-black rounded py-px px-1`}>!</code>).
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
@ -102,7 +105,7 @@ export default () => {
|
||||
save(name);
|
||||
}}
|
||||
/>
|
||||
<div className={'relative'}>
|
||||
<div css={tw`relative`}>
|
||||
<SpinnerOverlay visible={loading}/>
|
||||
<LazyAceEditor
|
||||
initialModePath={hash.replace(/^#/, '') || 'plain_text'}
|
||||
@ -113,18 +116,18 @@ export default () => {
|
||||
onContentSaved={() => save()}
|
||||
/>
|
||||
</div>
|
||||
<div className={'flex justify-end mt-4'}>
|
||||
<div css={tw`flex justify-end mt-4`}>
|
||||
{action === 'edit' ?
|
||||
<Can action={'file.update'}>
|
||||
<button className={'btn btn-primary btn-sm'} onClick={() => save()}>
|
||||
<Button onClick={() => save()}>
|
||||
Save Content
|
||||
</button>
|
||||
</Button>
|
||||
</Can>
|
||||
:
|
||||
<Can action={'file.create'}>
|
||||
<button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}>
|
||||
<Button onClick={() => setModalVisible(true)}>
|
||||
Create File
|
||||
</button>
|
||||
</Button>
|
||||
</Can>
|
||||
}
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { cleanDirectoryPath } from '@/helpers';
|
||||
import tw from 'twin.macro';
|
||||
|
||||
interface Props {
|
||||
withinFileEditor?: boolean;
|
||||
@ -32,11 +33,11 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={'flex items-center text-sm mb-4 text-neutral-500'}>
|
||||
/<span className={'px-1 text-neutral-300'}>home</span>/
|
||||
<div css={tw`flex items-center text-sm mb-4 text-neutral-500`}>
|
||||
/<span css={tw`px-1 text-neutral-300`}>home</span>/
|
||||
<NavLink
|
||||
to={`/server/${id}/files`}
|
||||
className={'px-1 text-neutral-200 no-underline hover:text-neutral-100'}
|
||||
css={tw`px-1 text-neutral-200 no-underline hover:text-neutral-100`}
|
||||
>
|
||||
container
|
||||
</NavLink>/
|
||||
@ -46,18 +47,18 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
|
||||
<React.Fragment key={index}>
|
||||
<NavLink
|
||||
to={`/server/${id}/files#${crumb.path}`}
|
||||
className={'px-1 text-neutral-200 no-underline hover:text-neutral-100'}
|
||||
css={tw`px-1 text-neutral-200 no-underline hover:text-neutral-100`}
|
||||
>
|
||||
{crumb.name}
|
||||
</NavLink>/
|
||||
</React.Fragment>
|
||||
:
|
||||
<span key={index} className={'px-1 text-neutral-300'}>{crumb.name}</span>
|
||||
<span key={index} css={tw`px-1 text-neutral-300`}>{crumb.name}</span>
|
||||
))
|
||||
}
|
||||
{file &&
|
||||
<React.Fragment>
|
||||
<span className={'px-1 text-neutral-300'}>{decodeURIComponent(file)}</span>
|
||||
<span css={tw`px-1 text-neutral-300`}>{decodeURIComponent(file)}</span>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
|
@ -14,7 +14,8 @@ import { Link } from 'react-router-dom';
|
||||
import Can from '@/components/elements/Can';
|
||||
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||
import ServerError from '@/components/screens/ServerError';
|
||||
import useRouter from 'use-react-router';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
const sortFiles = (files: FileObject[]): FileObject[] => {
|
||||
return files.sort((a, b) => a.name.localeCompare(b.name))
|
||||
@ -24,7 +25,7 @@ const sortFiles = (files: FileObject[]): FileObject[] => {
|
||||
export default () => {
|
||||
const [ error, setError ] = useState('');
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
const { clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
const { id } = ServerContext.useStoreState(state => state.server.data!);
|
||||
const { contents: files } = ServerContext.useStoreState(state => state.files);
|
||||
const { getDirectoryContents } = ServerContext.useStoreActions(actions => actions.files);
|
||||
@ -56,16 +57,16 @@ export default () => {
|
||||
|
||||
return (
|
||||
<PageContentBlock>
|
||||
<FlashMessageRender byKey={'files'} className={'mb-4'}/>
|
||||
<FlashMessageRender byKey={'files'} css={tw`mb-4`}/>
|
||||
<React.Fragment>
|
||||
<FileManagerBreadcrumbs/>
|
||||
{
|
||||
loading ?
|
||||
<Spinner size={'large'} centered={true}/>
|
||||
<Spinner size={'large'} centered/>
|
||||
:
|
||||
<React.Fragment>
|
||||
{!files.length ?
|
||||
<p className={'text-sm text-neutral-400 text-center'}>
|
||||
<p css={tw`text-sm text-neutral-400 text-center`}>
|
||||
This directory seems to be empty.
|
||||
</p>
|
||||
:
|
||||
@ -74,8 +75,8 @@ export default () => {
|
||||
<div>
|
||||
{files.length > 250 ?
|
||||
<React.Fragment>
|
||||
<div className={'rounded bg-yellow-400 mb-px p-3'}>
|
||||
<p className={'text-yellow-900 text-sm text-center'}>
|
||||
<div css={tw`rounded bg-yellow-400 mb-px p-3`}>
|
||||
<p css={tw`text-yellow-900 text-sm text-center`}>
|
||||
This directory is too large to display in the browser,
|
||||
limiting the output to the first 250 files.
|
||||
</p>
|
||||
@ -96,14 +97,15 @@ export default () => {
|
||||
</CSSTransition>
|
||||
}
|
||||
<Can action={'file.create'}>
|
||||
<div className={'flex justify-end mt-8'}>
|
||||
<div css={tw`flex justify-end mt-8`}>
|
||||
<NewDirectoryButton/>
|
||||
<Link
|
||||
<Button
|
||||
// @ts-ignore
|
||||
as={Link}
|
||||
to={`/server/${id}/files/new${window.location.hash}`}
|
||||
className={'btn btn-sm btn-primary'}
|
||||
>
|
||||
New File
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Can>
|
||||
</React.Fragment>
|
||||
|
@ -5,6 +5,8 @@ import { object, string } from 'yup';
|
||||
import Field from '@/components/elements/Field';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { join } from 'path';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
type Props = RequiredModalProps & {
|
||||
onFileNamed: (name: string) => void;
|
||||
@ -44,12 +46,10 @@ export default ({ onFileNamed, onDismissed, ...props }: Props) => {
|
||||
name={'fileName'}
|
||||
label={'File Name'}
|
||||
description={'Enter the name that this file should be saved as.'}
|
||||
autoFocus={true}
|
||||
autoFocus
|
||||
/>
|
||||
<div className={'mt-6 text-right'}>
|
||||
<button className={'btn btn-primary btn-sm'}>
|
||||
Create File
|
||||
</button>
|
||||
<div css={tw`mt-6 text-right`}>
|
||||
<Button>Create File</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
@ -10,6 +10,7 @@ import FileDropdownMenu from '@/components/server/files/FileDropdownMenu';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import useRouter from 'use-react-router';
|
||||
import tw from 'twin.macro';
|
||||
|
||||
export default ({ file }: { file: FileObject }) => {
|
||||
const directory = ServerContext.useStoreState(state => state.files.directory);
|
||||
@ -19,14 +20,11 @@ export default ({ file }: { file: FileObject }) => {
|
||||
return (
|
||||
<div
|
||||
key={file.name}
|
||||
className={`
|
||||
flex bg-neutral-700 rounded-sm mb-px text-sm
|
||||
hover:text-neutral-100 cursor-pointer items-center no-underline hover:bg-neutral-600
|
||||
`}
|
||||
css={tw`flex bg-neutral-700 rounded-sm mb-px text-sm hover:text-neutral-100 cursor-pointer items-center no-underline hover:bg-neutral-600`}
|
||||
>
|
||||
<NavLink
|
||||
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`}
|
||||
className={'flex flex-1 text-neutral-300 no-underline p-3'}
|
||||
css={tw`flex flex-1 text-neutral-300 no-underline p-3`}
|
||||
onClick={e => {
|
||||
// Don't rely on the onClick to work with the generated URL. Because of the way this
|
||||
// component re-renders you'll get redirected into a nested directory structure since
|
||||
@ -41,27 +39,27 @@ export default ({ file }: { file: FileObject }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={'flex-none text-neutral-400 mr-4 text-lg pl-3'}>
|
||||
<div css={tw`flex-none text-neutral-400 mr-4 text-lg pl-3`}>
|
||||
{file.isFile ?
|
||||
<FontAwesomeIcon icon={file.isSymlink ? faFileImport : faFileAlt}/>
|
||||
:
|
||||
<FontAwesomeIcon icon={faFolder}/>
|
||||
}
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
<div css={tw`flex-1`}>
|
||||
{file.name}
|
||||
</div>
|
||||
{file.isFile &&
|
||||
<div className={'w-1/6 text-right mr-4'}>
|
||||
<div css={tw`w-1/6 text-right mr-4`}>
|
||||
{bytesToHuman(file.size)}
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
className={'w-1/5 text-right mr-4'}
|
||||
css={tw`w-1/5 text-right mr-4`}
|
||||
title={file.modifiedAt.toString()}
|
||||
>
|
||||
{Math.abs(differenceInHours(file.modifiedAt, new Date())) > 48 ?
|
||||
format(file.modifiedAt, 'MMM Do, YYYY h:mma')
|
||||
format(file.modifiedAt, 'MMM do, yyyy h:mma')
|
||||
:
|
||||
formatDistanceToNow(file.modifiedAt, { addSuffix: true })
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import { join } from 'path';
|
||||
import { object, string } from 'yup';
|
||||
import createDirectory from '@/api/server/files/createDirectory';
|
||||
import v4 from 'uuid/v4';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
interface Values {
|
||||
directoryName: string;
|
||||
@ -62,33 +64,33 @@ export default () => {
|
||||
resetForm();
|
||||
}}
|
||||
>
|
||||
<Form className={'m-0'}>
|
||||
<Form css={tw`m-0`}>
|
||||
<Field
|
||||
id={'directoryName'}
|
||||
name={'directoryName'}
|
||||
label={'Directory Name'}
|
||||
/>
|
||||
<p className={'text-xs mt-2 text-neutral-400'}>
|
||||
<span className={'text-neutral-200'}>This directory will be created as</span>
|
||||
<p css={tw`text-xs mt-2 text-neutral-400`}>
|
||||
<span css={tw`text-neutral-200`}>This directory will be created as</span>
|
||||
/home/container/
|
||||
<span className={'text-cyan-200'}>
|
||||
<span css={tw`text-cyan-200`}>
|
||||
{decodeURIComponent(
|
||||
join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, ''),
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
<div className={'flex justify-end'}>
|
||||
<button className={'btn btn-sm btn-primary mt-8'}>
|
||||
<div css={tw`flex justify-end`}>
|
||||
<Button css={tw`mt-8`}>
|
||||
Create Directory
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
)}
|
||||
</Formik>
|
||||
<button className={'btn btn-sm btn-secondary mr-2'} onClick={() => setVisible(true)}>
|
||||
<Button isSecondary css={tw`mr-2`} onClick={() => setVisible(true)}>
|
||||
Create Directory
|
||||
</button>
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -6,7 +6,8 @@ import { join } from 'path';
|
||||
import renameFile from '@/api/server/files/renameFile';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import { FileObject } from '@/api/server/files/loadDirectory';
|
||||
import classNames from 'classnames';
|
||||
import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
|
||||
interface FormikValues {
|
||||
name: string;
|
||||
@ -48,14 +49,14 @@ export default ({ file, useMoveTerminology, ...props }: Props) => {
|
||||
>
|
||||
{({ isSubmitting, values }) => (
|
||||
<Modal {...props} dismissable={!isSubmitting} showSpinnerOverlay={isSubmitting}>
|
||||
<Form className={'m-0'}>
|
||||
<Form css={tw`m-0`}>
|
||||
<div
|
||||
className={classNames('flex', {
|
||||
'items-center': useMoveTerminology,
|
||||
'items-end': !useMoveTerminology,
|
||||
})}
|
||||
css={[
|
||||
tw`flex`,
|
||||
useMoveTerminology ? tw`items-center` : tw`items-end`,
|
||||
]}
|
||||
>
|
||||
<div className={'flex-1 mr-6'}>
|
||||
<div css={tw`flex-1 mr-6`}>
|
||||
<Field
|
||||
type={'string'}
|
||||
id={'file_name'}
|
||||
@ -65,18 +66,16 @@ export default ({ file, useMoveTerminology, ...props }: Props) => {
|
||||
? 'Enter the new name and directory of this file or folder, relative to the current directory.'
|
||||
: undefined
|
||||
}
|
||||
autoFocus={true}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button className={'btn btn-sm btn-primary'}>
|
||||
{useMoveTerminology ? 'Move' : 'Rename'}
|
||||
</button>
|
||||
<Button>{useMoveTerminology ? 'Move' : 'Rename'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
{useMoveTerminology &&
|
||||
<p className={'text-xs mt-2 text-neutral-400'}>
|
||||
<strong className={'text-neutral-200'}>New location:</strong>
|
||||
<p css={tw`text-xs mt-2 text-neutral-400`}>
|
||||
<strong css={tw`text-neutral-200`}>New location:</strong>
|
||||
/home/container/{join(directory, values.name).replace(/^(\.\.\/|\/)+/, '')}
|
||||
</p>
|
||||
}
|
||||
|
@ -119,6 +119,9 @@ module.exports = {
|
||||
transitionDuration: {
|
||||
250: '250ms',
|
||||
},
|
||||
minWidth: {
|
||||
'48': '12rem',
|
||||
},
|
||||
borderColor: theme => ({
|
||||
default: theme('colors.neutral.400', 'cuurrentColor'),
|
||||
}),
|
||||
|
Loading…
Reference in New Issue
Block a user