Skip to content

Commit 3790ad9

Browse files
committed
Support for file uploads
1 parent 1cb0615 commit 3790ad9

File tree

8 files changed

+164
-28
lines changed

8 files changed

+164
-28
lines changed

dev/index.html

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,61 @@
9898
.rjf-remove-button:hover + .rjf-form-group {
9999
background-color: #fff3f3;
100100
}
101+
.rjf-form-text {
102+
display: block;
103+
font-size: 0.88em;
104+
color: #777;
105+
margin-top: 8px;
106+
}
107+
.rjf-current-file-name {
108+
font-size: 0.9em;
109+
margin-top: 0.5em;
110+
margin-bottom: 0.5em;
111+
}
112+
.rjf-current-file-name span {
113+
display: inline-block;
114+
vertical-align: bottom;
115+
background-color: #eee;
116+
padding: 0 4px;
117+
max-width: 200px;
118+
overflow: hidden;
119+
text-overflow: ellipsis;
120+
white-space: nowrap;
121+
}
122+
.rjf-loader {
123+
width: 32px;
124+
height: 32px;
125+
clear: both;
126+
margin: 20px auto;
127+
display: inline-block;
128+
margin: 0;
129+
margin-right: 0.25em;
130+
height: 1em;
131+
width: 1em;
132+
vertical-align: middle;;
133+
border: 2px #a7e6f0 solid;
134+
border-top-color: #1191a6;
135+
border-radius: 50%;
136+
-webkit-animation: loaderRotate .4s infinite linear;
137+
animation: loaderRotate .4s infinite linear;
138+
}
139+
140+
@-webkit-keyframes loaderRotate {
141+
from {
142+
-webkit-transform: rotate(0deg);
143+
}
144+
to {
145+
-webkit-transform: rotate(359deg);
146+
}
147+
}
148+
@keyframes loaderRotate {
149+
from {
150+
transform: rotate(0deg);
151+
}
152+
to {
153+
transform: rotate(359deg);
154+
}
155+
}
101156
</style>
102157
</head>
103158
<body>
@@ -159,7 +214,8 @@ <h1>JSON Editor</h1>
159214
min_items: 0,
160215
items: {
161216
type: 'string',
162-
title: 'Tag'
217+
title: 'Tag',
218+
format: 'file-url'
163219
}
164220
},
165221
});

src/components/form.js

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import Button from './buttons';
2+
import Loader from './loaders';
3+
import {EditorContext, getCsrfCookie} from '../util';
24

35

46
export function FormInput({label, help_text, error, inputRef, ...props}) {
@@ -128,12 +130,15 @@ export function dataURItoBlob(dataURI) {
128130

129131

130132
export class FormFileInput extends React.Component {
133+
static contextType = EditorContext;
134+
131135
constructor(props) {
132136
super(props);
133137

134138
this.state = {
135139
value: props.value,
136-
fileName: this.getFileName()
140+
fileName: this.getFileName(),
141+
loading: false
137142
};
138143

139144
this.inputRef = React.createRef();
@@ -176,31 +181,71 @@ export class FormFileInput extends React.Component {
176181

177182
handleChange = (e) => {
178183
if (this.props.type === 'data-url') {
184+
let file = e.target.files[0];
185+
let fileName = file.name
179186

180-
}
181-
182-
let file = e.target.files[0];
183-
let fileName = file.name
187+
let reader = new FileReader();
184188

185-
let reader = new FileReader();
189+
reader.onload = () => {
186190

187-
reader.onload = () => {
191+
// this.setState({src: reader.result});
188192

189-
// this.setState({src: reader.result});
193+
// we create a fake event object
194+
let event = {
195+
target: {
196+
type: 'text',
197+
value: this.addNameToDataURL(reader.result, fileName),
198+
name: this.props.name
199+
}
200+
};
190201

191-
// we create a fake event object
192-
let event = {
193-
target: {
194-
type: 'text',
195-
value: this.addNameToDataURL(reader.result, fileName),
196-
name: this.props.name
197-
}
198-
};
202+
this.props.onChange(event);
199203

200-
this.props.onChange(event);
204+
}
205+
reader.readAsDataURL(file);
206+
} else if (this.props.type === 'file-url') {
207+
let endpoint = this.context.fileUploadEndpoint;
208+
if (!endpoint) {
209+
console.error(
210+
"Error: fileUploadEndpoint option need to be passed "
211+
+ "while initializing editor for enabling file uploads.");
212+
alert("Files can't be uploaded.");
213+
return;
214+
}
215+
216+
this.setState({loading: true});
217+
218+
let formData = new FormData();
219+
formData.append('file', e.target.files[0]);
220+
221+
fetch(endpoint, {
222+
method: 'POST',
223+
headers: {
224+
'X-CSRFToken': getCsrfCookie(),
225+
},
226+
body: formData
227+
})
228+
.then((response) => response.json())
229+
.then((result) => {
230+
// we create a fake event object
231+
let event = {
232+
target: {
233+
type: 'text',
234+
value: result.file_path,
235+
name: this.props.name
236+
}
237+
};
238+
239+
this.props.onChange(event);
240+
this.setState({loading: false});
241+
})
242+
.catch((error) => {
243+
alert('Something went wrong while uploading file');
244+
console.error('Error:', error);
245+
this.setState({loading: false});
246+
});
201247

202248
}
203-
reader.readAsDataURL(file);
204249

205250
}
206251

@@ -220,10 +265,14 @@ export class FormFileInput extends React.Component {
220265
{this.state.value &&
221266
<div className="rjf-current-file-name">Current file: <span>{this.state.fileName}</span></div>
222267
}
223-
{this.state.value && 'Change:'}
268+
{this.state.value && !this.state.loading && 'Change:'}
269+
{this.state.loading ?
270+
<div className="rjf-file-field-loading"><Loader/> Uploading...</div>
271+
:
224272
<div className="rjf-file-field-input">
225273
<FormInput {...props} inputRef={this.inputRef} />
226274
</div>
275+
}
227276
</div>
228277
</div>
229278
);

src/components/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import Button from './buttons';
22
import {FormInput, FormCheckInput, FormRadioInput, FormSelectInput, FormFileInput} from './form';
33
import {FormRow, FormGroup} from './containers';
4+
import Loader from './loaders';
45

56
export {
67
Button,
78
FormInput, FormCheckInput, FormRadioInput, FormSelectInput, FormFileInput,
8-
FormRow, FormGroup
9+
FormRow, FormGroup,
10+
Loader,
911
};

src/components/loaders.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Loader (props) {
2+
return <div className="rjf-loader"></div>;
3+
}

src/data.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ export function getBlankObject(schema) {
55
let value = schema.keys[key];
66
let type = value.type;
77

8-
if (type === 'string')
9-
keys[key] = '';
10-
else if (type === 'array')
8+
if (type === 'array')
119
keys[key] = getBlankArray(value);
1210
else if (type === 'object')
1311
keys[key] = getBlankObject(value);
12+
else if (type === 'string')
13+
keys[key] = '';
14+
else if (schema.type === 'number')
15+
return '';
1416
}
1517

1618
return keys;
@@ -21,12 +23,14 @@ export function getBlankArray(schema) {
2123
let items = [];
2224
let type = schema.items.type;
2325

24-
if (type === 'string')
25-
items.push('');
26-
else if (type === 'array')
26+
if (type === 'array')
2727
items.push(getBlankArray(schema.items))
2828
else if (type === 'object')
2929
items.push(getBlankObject(schema.items));
30+
else if (type === 'string')
31+
items.push('');
32+
else if (schema.type === 'number')
33+
items.push('');
3034

3135
return items;
3236
}
@@ -40,6 +44,8 @@ export function getBlankData(schema) {
4044
return getBlankObject(schema);
4145
} else if (schema.type === 'string') {
4246
return '';
47+
} else if (schema.type === 'number') {
48+
return '';
4349
}
4450
}
4551

src/form.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {getBlankData, getSyncedData} from './data';
22
import {getArrayFormRow, getObjectFormRow} from './ui';
3+
import {EditorContext} from './util';
34

45

56
export default class Form extends React.Component {
@@ -170,7 +171,13 @@ export default class Form extends React.Component {
170171
return (
171172
<div className="rjf-form-wrapper">
172173
<fieldset className="module aligned">
174+
<EditorContext.Provider
175+
value={{
176+
fileUploadEndpoint: this.props.fileUploadEndpoint
177+
}}
178+
>
173179
{this.getFields()}
180+
</EditorContext.Provider>
174181
</fieldset>
175182
</div>
176183
);

src/renderer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ export default function JSONForm(config) {
66
this.dataInputId = config.dataInputId;
77
this.schema = config.schema;
88
this.data = config.data;
9+
this.fileUploadEndpoint = config.fileUploadEndpoint;
910

1011
this.render = function() {
1112
ReactDOM.render(
1213
<Form
1314
schema={this.schema}
1415
dataInputId={this.dataInputId}
1516
data={this.data}
17+
fileUploadEndpoint={this.fileUploadEndpoint}
1618
/>,
1719
document.getElementById(this.containerId)
1820
);

src/util.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
export const EditorContext = React.createContext();
2+
3+
14
export function capitalize(string) {
25
if (!string)
36
return '';
@@ -12,4 +15,12 @@ export function getVerboseName(name) {
1215

1316
name = name.replace(/_/g, ' ');
1417
return capitalize(name);
15-
}
18+
}
19+
20+
21+
export function getCsrfCookie() {
22+
if ((document.cookie.split(';').filter((item) => item.trim().indexOf('csrftoken=') === 0)).length) {
23+
return document.cookie.split(';').filter((item) => item.trim().indexOf('csrftoken=') === 0)[0].split('=')[1];
24+
}
25+
return null;
26+
}

0 commit comments

Comments
 (0)