Skip to content

Commit 2dfc326

Browse files
committed
Implement file upload by drag and drop and paste. Table formatting
Move file description from below the file upload into the table on same line as file upload. Implement https://wiki.roundup-tracker.org/FileUploadViaDragDropAndPaste for table structure. Added creation of description field along with file input field. On tables align header fields that do not span rows (i.e. column headers) on the left. Table titles/grouping span multiple columns. Also do not align column headers left on the index page which uses the .list class. Also add some padding to th to increase space and improve readability. For message and files tables make them 95% of the width of their container. It makes the headers for messages and files more readable by adding space.
1 parent 3097575 commit 2dfc326

File tree

2 files changed

+214
-5
lines changed

2 files changed

+214
-5
lines changed

website/issues/html/issue.item.html

Lines changed: 200 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,19 @@
157157

158158
<tr tal:condition="context/is_edit_ok">
159159
<th><label for="file-1@content" i18n:translate="">File</label>:</th>
160-
<td colspan=3>
160+
<td>
161161
<input type="hidden" name="@link@files" value="file-1">
162162
<input type="file" id="file-1@content" name="file-1@content" size="40">
163163
</td>
164+
<th><label for="file-1@description" i18n:translate="">File Description</label>:</th>
165+
<td colspan=3><input type="edit" id="file-1@description" name="file-1@description" size="40"></td>
164166
</tr>
165167
<tr tal:condition="context/is_edit_ok">
166-
<th><label for="file-1@description" i18n:translate="">File Description</label>:</th>
167-
<td colspan=3><input type="edit" id="file-1@description" name="file-1@description" size="40"></td>
168+
<td colspan=4>
169+
<textarea readonly id="DropZone">
170+
paste images or drag and drop files here....
171+
</textarea>
172+
</td>
168173
</tr>
169174
</table>
170175
</fieldset>
@@ -184,6 +189,197 @@
184189
</table>
185190
</form>
186191

192+
<!-- drag and drop code -->
193+
<script tal:attributes="nonce request/client/client_nonce">
194+
/* multiple file drops cause issues with redefined
195+
file-X@content issues. input multiple assumes
196+
it can start numbering from 1 for each of the
197+
multiple files. However numbering here does the
198+
same leading to duplicate file-2@content.
199+
200+
layout needs some work, alignnment of new file
201+
input's isn't great.
202+
203+
Need a way to delete or reset file inputs so file
204+
assigned to them isn't uploaded. Clicking on button
205+
in chrome and then canceling unsets the file. But this
206+
sequence does nothing in firefox.
207+
208+
Pasting always uses image.<type> can't name file.
209+
Need to query user during paste for name/description.
210+
*/
211+
212+
let newInput=null;
213+
let NextInputNum = 100; /* file input 1 is hardcoded in form.
214+
It is a multiple file input control. To
215+
prevent collision, we start dynamic file controls at
216+
file-100@content. 100 is larger than we expect
217+
the number of files uploaded using file input 1.*/
218+
219+
let target = document.getElementById('DropZone');
220+
target.style.display = "block";
221+
let body = document.body;
222+
let fileInput = document.getElementById('file-1@content');
223+
let fileDesc = document.getElementById('file-1@description');
224+
225+
function add_file_input () {
226+
227+
// Only allow one change listener on newest input.
228+
fileInput.removeEventListener('change',
229+
add_file_input,
230+
false);
231+
232+
/* create new file input to get next dragged file */
233+
/* <input type="file" name="file-2@content"> for 2,
234+
3, 4, ... */
235+
newInput=document.createElement('input');
236+
newInput.type="file";
237+
newInput.id="file-" + NextInputNum +"@content";
238+
newInput.name=newInput.id;
239+
fileInput = fileInput.insertAdjacentElement('afterend',
240+
newInput);
241+
// add change hander to newest file input
242+
fileInput.addEventListener('change',
243+
add_file_input, // create new input for more files
244+
false);
245+
246+
/* link file-N to list of files on issue.
247+
also link to msg-1 */
248+
addLink=document.createElement('input');
249+
addLink.type="hidden";
250+
addLink.id="@link@file=file-" + NextInputNum;
251+
addLink.name="@link@files"
252+
addLink.value="file-" + NextInputNum;
253+
fileInput.insertAdjacentElement('afterend', addLink);
254+
255+
addLink=document.createElement('input');
256+
addLink.type="hidden";
257+
addLink.id="msg-1@link@files=file-" + NextInputNum;
258+
addLink.name="msg-1@link@files"
259+
addLink.value="file-" + NextInputNum
260+
fileInput.insertAdjacentElement('afterend', addLink);
261+
262+
addLink=document.createElement('input');
263+
addLink.type="edit";
264+
addLink.id="file-" + NextInputNum + "@description";
265+
addLink.name=addLink.id
266+
addLink.size = 40
267+
fileDesc=fileDesc.insertAdjacentElement('afterend', addLink);
268+
269+
NextInputNum = NextInputNum+1;
270+
271+
}
272+
273+
function MarkDropZone(e, active) {
274+
active == true ? e.style.backgroundColor = "goldenrod" :
275+
e.style.backgroundColor = "";
276+
}
277+
fileInput.addEventListener('change',
278+
add_file_input, // create new input for more files
279+
false);
280+
281+
target.addEventListener('dragover', (e) => {
282+
e.preventDefault();
283+
body.classList.add('dragging');
284+
});
285+
286+
target.addEventListener('dragenter', (e) => {
287+
e.preventDefault();
288+
MarkDropZone(target, true);
289+
});
290+
291+
292+
target.addEventListener('dragleave', (e) => {
293+
e.preventDefault();
294+
MarkDropZone(target, false);
295+
});
296+
297+
target.addEventListener('dragleave', () => {
298+
body.classList.remove('dragging');
299+
});
300+
301+
target.addEventListener('drop', (e) => {
302+
body.classList.remove('dragging');
303+
MarkDropZone(target, false);
304+
305+
// Only allow single file drop unless
306+
// fileInput name is @file that can support
307+
// multiple file drop and file drop is multiple.
308+
if (( fileInput.name != "@file" ||
309+
! fileInput.hasAttribute('multiple')) &&
310+
e.dataTransfer.files.length != 1 ) {
311+
alert("File input can only accept one file.")
312+
e.preventDefault();
313+
return
314+
}
315+
// set file input files to the dragged files
316+
fileInput.files = e.dataTransfer.files;
317+
318+
add_file_input(); // create new input for more files
319+
// run last otherwise firefox empties e.dataTransfer
320+
e.preventDefault();
321+
});
322+
323+
target.addEventListener('mouseover', (e) => {
324+
e.preventDefault();
325+
MarkDropZone(target, true);
326+
});
327+
328+
target.addEventListener('mouseout', (e) => {
329+
e.preventDefault();
330+
MarkDropZone(target, false);
331+
});
332+
333+
target.addEventListener('paste', (event) => {
334+
// https://mobiarch.wordpress.com/2013/09/25/upload-image-by-copy-and-paste/
335+
// add paste event listener to the page
336+
337+
// https://stackoverflow.com/questions/50427513/
338+
// html-paste-clipboard-image-to-file-input
339+
if ( event.clipboardData.files.length == 0) {
340+
// if not file data alert
341+
alert("No image found for pasting");
342+
event.preventDefault();
343+
return;
344+
}
345+
fileInput.files = event.clipboardData.files;
346+
347+
/* Possible enhancement if file check fails.
348+
iterate over all items 0 ...:
349+
event.clipboardData.items.length
350+
look at all items[i].kind for 'string' and
351+
items[i].type looking for a text/plain item. If
352+
found,
353+
event.clipboardData.items[1].getAsString(
354+
callback_fcn(s))
355+
356+
where callback function that creates a new
357+
dataTransfer object with a file and insert the
358+
content s and assigns it to the input.
359+
360+
https://gist.github.com/guest271314/7eac2c21911f5e40f489\33ac78e518bd
361+
*/
362+
add_file_input(); // create new input for more files
363+
// do not paste contents to dropzone
364+
event.preventDefault();
365+
}, false);
366+
</script>
367+
<style tal:attributes="nonce request/client/client_nonce">
368+
#FileArea input[type=file] ~ input[type=file] {display:block;}
369+
#DropZone { /* don't display dropzone by default.
370+
Displayed as block by javascript. */
371+
display:none;
372+
width: 100%;
373+
/* override textarea inset */
374+
border-style: dashed;
375+
padding: 3ex 0; /* larger dropzone */
376+
/* add space below inputs */
377+
margin-block-start: 1em;
378+
/* lighter color */
379+
background: rgba(255,255,255,0.4);
380+
}
381+
</style>
382+
187383
<p tal:condition="context/id" i18n:translate="">
188384
Created on <b><tal:x replace="python:context.creation.pretty('%Y-%m-%d %H:%M')" i18n:name="creation" /></b>
189385
by <b><tal:x replace="context/creator" i18n:name="creator" /></b>,
@@ -259,3 +455,4 @@
259455
</td>
260456

261457
</tal:block>
458+
<!-- SHA: ad841842c0da5f9d1a7f69a1e0c847a549b75bf2 -->

website/issues/html/style.css

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,23 @@ table
151151
background-color: #fafafa;
152152
}
153153

154+
table:not(.list) th {
155+
text-align: left;
156+
}
157+
158+
table th[colspan] {
159+
text-align: center;
160+
}
154161
/* Need some space between content of Issue List columns */
155-
td { padding-left: 1em; }
162+
td, th { padding-left: 1em; }
156163

157164
tr.odd { background-color:#f5f5f5; }
158165

166+
/* widen tables since headers have a lot of info and are squished
167+
and difficult to read */
168+
table.messages, table.files {
169+
width: 95%;
170+
}
159171
/* Make sure that user name starts at the top of the change list
160172
and not in the middle Issue2550809: History display misalignment */
161173
table.history td {
@@ -175,4 +187,4 @@ table.classhelp td {
175187
font-weight: bold;
176188
}
177189

178-
/* SHA: 01691a9346b3bc6a29a505a2dfa389ee3edc950f */
190+
/* SHA: 2a5330c3d1ee6bf31e672e1c8a9317d11b7eb436 */

0 commit comments

Comments
 (0)