Skip to content

Commit 78ccdf9

Browse files
committed
Square away saving of existing files
1 parent 0dff732 commit 78ccdf9

File tree

5 files changed

+134
-54
lines changed

5 files changed

+134
-54
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import http from '@/api/http';
2+
3+
export default (uuid: string, file: string, content: string): Promise<void> => {
4+
return new Promise((resolve, reject) => {
5+
http.post(
6+
`/api/client/servers/${uuid}/files/write`,
7+
content,
8+
{
9+
params: { file },
10+
headers: {
11+
'Content-Type': 'text/plain',
12+
},
13+
},
14+
)
15+
.then(() => resolve())
16+
.catch(reject);
17+
});
18+
};

resources/scripts/components/elements/AceEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export default ({ style, initialContent, initialModePath, fetchContent, onConten
113113
return (
114114
<EditorContainer style={style}>
115115
<div id={'editor'} ref={ref}/>
116-
<div className={'absolute pin-r pin-t z-50'}>
116+
<div className={'absolute pin-r pin-b z-50'}>
117117
<div className={'m-3 rounded bg-neutral-900 border border-black'}>
118118
<select
119119
className={'input-dark'}

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

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,70 @@ import React, { lazy, useEffect, useState } from 'react';
22
import { ServerContext } from '@/state/server';
33
import getFileContents from '@/api/server/files/getFileContents';
44
import useRouter from 'use-react-router';
5+
import { Actions, useStoreState } from 'easy-peasy';
6+
import { ApplicationStore } from '@/state';
7+
import { httpErrorToHuman } from '@/api/http';
8+
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
9+
import saveFileContents from '@/api/server/files/saveFileContents';
10+
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
511

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

814
export default () => {
915
const { location: { hash } } = useRouter();
16+
const [ loading, setLoading ] = useState(true);
1017
const [ content, setContent ] = useState('');
18+
1119
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
20+
const addError = useStoreState((state: Actions<ApplicationStore>) => state.flashes.addError);
1221

13-
let ref: null| (() => Promise<string>) = null;
22+
let fetchFileContent: null | (() => Promise<string>) = null;
1423

1524
useEffect(() => {
1625
getFileContents(uuid, hash.replace(/^#/, ''))
1726
.then(setContent)
18-
.catch(error => console.error(error));
27+
.catch(error => console.error(error))
28+
.then(() => setLoading(false));
1929
}, [ uuid, hash ]);
2030

31+
const save = (e: React.MouseEvent<HTMLButtonElement>) => {
32+
if (!fetchFileContent) {
33+
return;
34+
}
35+
36+
setLoading(true);
37+
fetchFileContent()
38+
.then(content => {
39+
return saveFileContents(uuid, hash.replace(/^#/, ''), content);
40+
})
41+
.catch(error => {
42+
console.error(error);
43+
addError({ message: httpErrorToHuman(error), key: 'files' });
44+
})
45+
.then(() => setLoading(false));
46+
};
47+
2148
return (
22-
<div className={'my-10 mb-4'}>
23-
<LazyAceEditor
24-
initialModePath={hash.replace(/^#/, '') || 'plain_text'}
25-
initialContent={content}
26-
fetchContent={value => {
27-
ref = value;
28-
}}
29-
onContentSaved={() => null}
30-
/>
49+
<div className={'mt-10 mb-4'}>
50+
<FileManagerBreadcrumbs withinFileEditor={true}/>
51+
<div className={'relative'}>
52+
<SpinnerOverlay visible={loading}/>
53+
<LazyAceEditor
54+
initialModePath={hash.replace(/^#/, '') || 'plain_text'}
55+
initialContent={content}
56+
fetchContent={value => {
57+
fetchFileContent = value;
58+
}}
59+
onContentSaved={() => null}
60+
/>
61+
</div>
62+
{content &&
3163
<div className={'flex justify-end mt-4'}>
32-
<button className={'btn btn-primary btn-sm'}>
64+
<button className={'btn btn-primary btn-sm'} onClick={save}>
3365
Save Content
3466
</button>
3567
</div>
68+
}
3669
</div>
3770
);
3871
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { ServerContext } from '@/state/server';
3+
import { NavLink } from 'react-router-dom';
4+
5+
export default ({ withinFileEditor }: { withinFileEditor?: boolean }) => {
6+
const [ file, setFile ] = useState<string | null>(null);
7+
const id = ServerContext.useStoreState(state => state.server.data!.id);
8+
const directory = ServerContext.useStoreState(state => state.files.directory);
9+
const setDirectory = ServerContext.useStoreActions(actions => actions.files.setDirectory);
10+
11+
useEffect(() => {
12+
const parts = window.location.hash.replace(/^#(\/)*/, '/').split('/');
13+
14+
if (withinFileEditor) {
15+
setFile(parts.pop() || null);
16+
}
17+
18+
setDirectory(parts.join('/'));
19+
}, [ withinFileEditor, setDirectory ]);
20+
21+
const breadcrumbs = (): { name: string; path?: string }[] => directory.split('/')
22+
.filter(directory => !!directory)
23+
.map((directory, index, dirs) => {
24+
if (!withinFileEditor && index === dirs.length - 1) {
25+
return { name: directory };
26+
}
27+
28+
return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` };
29+
});
30+
31+
return (
32+
<div className={'flex items-center text-sm mb-4 text-neutral-500'}>
33+
/<span className={'px-1 text-neutral-300'}>home</span>/
34+
<NavLink
35+
to={`/server/${id}/files`}
36+
onClick={() => setDirectory('/')}
37+
className={'px-1 text-neutral-200 no-underline hover:text-neutral-100'}
38+
>
39+
container
40+
</NavLink>/
41+
{
42+
breadcrumbs().map((crumb, index) => (
43+
crumb.path ?
44+
<React.Fragment key={index}>
45+
<NavLink
46+
to={`/server/${id}/files#${crumb.path}`}
47+
onClick={() => setDirectory(crumb.path!)}
48+
className={'px-1 text-neutral-200 no-underline hover:text-neutral-100'}
49+
>
50+
{crumb.name}
51+
</NavLink>/
52+
</React.Fragment>
53+
:
54+
<span key={index} className={'px-1 text-neutral-300'}>{crumb.name}</span>
55+
))
56+
}
57+
{file &&
58+
<React.Fragment>
59+
<span className={'px-1 text-neutral-300'}>{file}</span>
60+
</React.Fragment>
61+
}
62+
</div>
63+
);
64+
};

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

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import { httpErrorToHuman } from '@/api/http';
77
import { CSSTransition } from 'react-transition-group';
88
import Spinner from '@/components/elements/Spinner';
99
import FileObjectRow from '@/components/server/files/FileObjectRow';
10+
import FileManagerBreadcrumbs from '@/components/server/files/FileManagerBreadcrumbs';
1011

1112
export default () => {
1213
const [ loading, setLoading ] = useState(true);
1314
const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
1415
const { contents: files, directory } = ServerContext.useStoreState(state => state.files);
15-
const { setDirectory, getDirectoryContents } = ServerContext.useStoreActions(actions => actions.files);
16+
const { getDirectoryContents } = ServerContext.useStoreActions(actions => actions.files);
1617

17-
const load = () => {
18+
useEffect(() => {
1819
setLoading(true);
1920
clearFlashes();
2021

@@ -24,50 +25,14 @@ export default () => {
2425
console.error(error.message, { error });
2526
addError({ message: httpErrorToHuman(error), key: 'files' });
2627
});
27-
};
28-
29-
const breadcrumbs = (): { name: string; path?: string }[] => directory.split('/')
30-
.filter(directory => !!directory)
31-
.map((directory, index, dirs) => {
32-
if (index === dirs.length - 1) {
33-
return { name: directory };
34-
}
35-
36-
return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` };
37-
});
38-
39-
useEffect(() => load(), [ directory ]);
28+
// eslint-disable-next-line react-hooks/exhaustive-deps
29+
}, [ directory ]);
4030

4131
return (
4232
<div className={'my-10 mb-6'}>
4333
<FlashMessageRender byKey={'files'} className={'mb-4'}/>
4434
<React.Fragment>
45-
<div className={'flex items-center text-sm mb-4 text-neutral-500'}>
46-
/<span className={'px-1 text-neutral-300'}>home</span>/
47-
<a
48-
href={'#'}
49-
onClick={() => setDirectory('/')}
50-
className={'px-1 text-neutral-200 no-underline hover:text-neutral-100'}
51-
>
52-
container
53-
</a>/
54-
{
55-
breadcrumbs().map((crumb, index) => (
56-
crumb.path ?
57-
<React.Fragment key={index}>
58-
<a
59-
href={`#${crumb.path}`}
60-
onClick={() => setDirectory(crumb.path!)}
61-
className={'px-1 text-neutral-200 no-underline hover:text-neutral-100'}
62-
>
63-
{crumb.name}
64-
</a>/
65-
</React.Fragment>
66-
:
67-
<span key={index} className={'px-1 text-neutral-300'}>{crumb.name}</span>
68-
))
69-
}
70-
</div>
35+
<FileManagerBreadcrumbs/>
7136
{
7237
loading ?
7338
<Spinner size={'large'} centered={true}/>

0 commit comments

Comments
 (0)