Skip to content

Commit e784218

Browse files
committed
Add support for creating a new file
1 parent d750731 commit e784218

File tree

6 files changed

+116
-23
lines changed

6 files changed

+116
-23
lines changed

resources/scripts/components/elements/AceEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export interface Props {
5959
}
6060

6161
export default ({ style, initialContent, initialModePath, fetchContent, onContentSaved }: Props) => {
62-
const [ mode, setMode ] = useState('plain_text');
62+
const [ mode, setMode ] = useState('ace/mode/plain_text');
6363

6464
const [ editor, setEditor ] = useState<Editor>();
6565
const ref = useCallback(node => {

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

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,49 @@ import { httpErrorToHuman } from '@/api/http';
88
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
99
import saveFileContents from '@/api/server/files/saveFileContents';
1010
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
11+
import { useParams } from 'react-router';
12+
import FileNameModal from '@/components/server/files/FileNameModal';
1113

1214
const LazyAceEditor = lazy(() => import(/* webpackChunkName: "editor" */'@/components/elements/AceEditor'));
1315

1416
export default () => {
15-
const { location: { hash } } = useRouter();
16-
const [ loading, setLoading ] = useState(true);
17+
const { action } = useParams();
18+
const { history, location: { hash } } = useRouter();
19+
const [ loading, setLoading ] = useState(action === 'edit');
1720
const [ content, setContent ] = useState('');
21+
const [ modalVisible, setModalVisible ] = useState(false);
1822

19-
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
23+
const { id, uuid } = ServerContext.useStoreState(state => state.server.data!);
2024
const addError = useStoreState((state: Actions<ApplicationStore>) => state.flashes.addError);
2125

2226
let fetchFileContent: null | (() => Promise<string>) = null;
2327

24-
useEffect(() => {
25-
getFileContents(uuid, hash.replace(/^#/, ''))
26-
.then(setContent)
27-
.catch(error => console.error(error))
28-
.then(() => setLoading(false));
29-
}, [ uuid, hash ]);
28+
if (action !== 'new') {
29+
useEffect(() => {
30+
getFileContents(uuid, hash.replace(/^#/, ''))
31+
.then(setContent)
32+
.catch(error => console.error(error))
33+
.then(() => setLoading(false));
34+
}, [ uuid, hash ]);
35+
}
3036

31-
const save = (e: React.MouseEvent<HTMLButtonElement>) => {
37+
const save = (name?: string) => {
3238
if (!fetchFileContent) {
3339
return;
3440
}
3541

3642
setLoading(true);
3743
fetchFileContent()
3844
.then(content => {
39-
return saveFileContents(uuid, hash.replace(/^#/, ''), content);
45+
return saveFileContents(uuid, name || hash.replace(/^#/, ''), content);
46+
})
47+
.then(() => {
48+
if (name) {
49+
history.push(`/server/${id}/files/edit#${hash.replace(/^#/, '')}/${name}`);
50+
return;
51+
}
52+
53+
return Promise.resolve();
4054
})
4155
.catch(error => {
4256
console.error(error);
@@ -47,7 +61,15 @@ export default () => {
4761

4862
return (
4963
<div className={'mt-10 mb-4'}>
50-
<FileManagerBreadcrumbs withinFileEditor={true}/>
64+
<FileManagerBreadcrumbs withinFileEditor={true} isNewFile={action !== 'edit'}/>
65+
<FileNameModal
66+
visible={modalVisible}
67+
onDismissed={() => setModalVisible(false)}
68+
onFileNamed={(name) => {
69+
setModalVisible(false);
70+
save(name);
71+
}}
72+
/>
5173
<div className={'relative'}>
5274
<SpinnerOverlay visible={loading}/>
5375
<LazyAceEditor
@@ -60,9 +82,15 @@ export default () => {
6082
/>
6183
</div>
6284
<div className={'flex justify-end mt-4'}>
63-
<button className={'btn btn-primary btn-sm'} onClick={save}>
64-
Save Content
65-
</button>
85+
{action === 'edit' ?
86+
<button className={'btn btn-primary btn-sm'} onClick={() => save()}>
87+
Save Content
88+
</button>
89+
:
90+
<button className={'btn btn-primary btn-sm'} onClick={() => setModalVisible(true)}>
91+
Create File
92+
</button>
93+
}
6694
</div>
6795
</div>
6896
);

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import React, { useEffect, useState } from 'react';
22
import { ServerContext } from '@/state/server';
3-
import { NavLink } from 'react-router-dom';
3+
import { NavLink, useParams } from 'react-router-dom';
44

5-
export default ({ withinFileEditor }: { withinFileEditor?: boolean }) => {
5+
interface Props {
6+
withinFileEditor?: boolean;
7+
isNewFile?: boolean;
8+
}
9+
10+
export default ({ withinFileEditor, isNewFile }: Props) => {
11+
const { action } = useParams();
612
const [ file, setFile ] = useState<string | null>(null);
713
const id = ServerContext.useStoreState(state => state.server.data!.id);
814
const directory = ServerContext.useStoreState(state => state.files.directory);
@@ -11,12 +17,12 @@ export default ({ withinFileEditor }: { withinFileEditor?: boolean }) => {
1117
useEffect(() => {
1218
const parts = window.location.hash.replace(/^#(\/)*/, '/').split('/');
1319

14-
if (withinFileEditor) {
20+
if (withinFileEditor && !isNewFile) {
1521
setFile(parts.pop() || null);
1622
}
1723

1824
setDirectory(parts.join('/'));
19-
}, [ withinFileEditor, setDirectory ]);
25+
}, [ withinFileEditor, isNewFile, setDirectory ]);
2026

2127
const breadcrumbs = (): { name: string; path?: string }[] => directory.split('/')
2228
.filter(directory => !!directory)
@@ -28,6 +34,9 @@ export default ({ withinFileEditor }: { withinFileEditor?: boolean }) => {
2834
return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` };
2935
});
3036

37+
if (withinFileEditor)
38+
console.log(breadcrumbs());
39+
3140
return (
3241
<div className={'flex items-center text-sm mb-4 text-neutral-500'}>
3342
/<span className={'px-1 text-neutral-300'}>home</span>/

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import FileObjectRow from '@/components/server/files/FileObjectRow';
1010
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
1111
import { FileObject } from '@/api/server/files/loadDirectory';
1212
import NewDirectoryButton from '@/components/server/files/NewDirectoryButton';
13+
import { Link } from 'react-router-dom';
1314

1415
const sortFiles = (files: FileObject[]): FileObject[] => {
1516
return files.sort((a, b) => a.name.localeCompare(b.name))
@@ -19,6 +20,7 @@ const sortFiles = (files: FileObject[]): FileObject[] => {
1920
export default () => {
2021
const [ loading, setLoading ] = useState(true);
2122
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
23+
const { id } = ServerContext.useStoreState(state => state.server.data!);
2224
const { contents: files, directory } = ServerContext.useStoreState(state => state.files);
2325
const { getDirectoryContents } = ServerContext.useStoreActions(actions => actions.files);
2426

@@ -79,9 +81,9 @@ export default () => {
7981
}
8082
<div className={'flex justify-end mt-8'}>
8183
<NewDirectoryButton/>
82-
<button className={'btn btn-sm btn-primary'}>
84+
<Link to={`/server/${id}/files/new${window.location.hash}`} className={'btn btn-sm btn-primary'}>
8385
New File
84-
</button>
86+
</Link>
8587
</div>
8688
</React.Fragment>
8789
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
3+
import { Form, Formik, FormikActions } from 'formik';
4+
import { object, string } from 'yup';
5+
import Field from '@/components/elements/Field';
6+
7+
type Props = RequiredModalProps & {
8+
onFileNamed: (name: string) => void;
9+
};
10+
11+
interface Values {
12+
fileName: string;
13+
}
14+
15+
export default ({ onFileNamed, onDismissed, ...props }: Props) => {
16+
const submit = (values: Values, { setSubmitting }: FormikActions<Values>) => {
17+
onFileNamed(values.fileName);
18+
setSubmitting(false);
19+
};
20+
21+
return (
22+
<Formik
23+
onSubmit={submit}
24+
initialValues={{ fileName: '' }}
25+
validationSchema={object().shape({
26+
fileName: string().required().min(1),
27+
})}
28+
>
29+
{({ resetForm }) => (
30+
<Modal
31+
onDismissed={() => {
32+
resetForm();
33+
onDismissed();
34+
}}
35+
{...props}
36+
>
37+
<Form>
38+
<Field
39+
id={'fileName'}
40+
name={'fileName'}
41+
label={'File Name'}
42+
description={'Enter the name that this file should be saved as.'}
43+
/>
44+
<div className={'mt-6 text-right'}>
45+
<button className={'btn btn-primary btn-sm'}>
46+
Create File
47+
</button>
48+
</div>
49+
</Form>
50+
</Modal>
51+
)}
52+
</Formik>
53+
);
54+
};

resources/scripts/routers/ServerRouter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
5858
<Route path={`${match.path}`} component={ServerConsole} exact/>
5959
<Route path={`${match.path}/files`} component={FileManagerContainer} exact/>
6060
<Route
61-
path={`${match.path}/files/edit`}
61+
path={`${match.path}/files/:action(edit|new)`}
6262
render={props => (
6363
<SuspenseSpinner>
6464
<FileEditContainer {...props as any}/>

0 commit comments

Comments
 (0)