implement drag and drop file upload

This commit is contained in:
joshuaboud 2021-06-03 13:34:52 -03:00
parent 9800280ec2
commit ee6fc26ffc
No known key found for this signature in database
GPG Key ID: 17EFB59E2A8BF50E
4 changed files with 290 additions and 1 deletions

View File

@ -207,6 +207,7 @@ input[type="text"] {
align-items: flex-start;
align-content: flex-start;
overflow: auto;
position: relative;
}
.nav-item {
@ -486,3 +487,30 @@ input:checked + .slider:before {
.nav-context-menu-item:hover {
background-color: var(--border);
}
.drag-enter {
border: 1px dashed var(--border);
}
.nav-notifications {
position: absolute;
bottom: 0;
right: 0;
padding: 5px;
display: flex;
flex-flow: column-reverse nowrap;
}
.nav-notification {
z-index: 10;
flex-grow: 0;
padding: 5px;
background-color: var(--container);
border: 1px solid var(--border);
color: var(--font);
}
.nav-notification-header {
font-size: 120%;
font-weight: bold;
}

View File

@ -64,7 +64,10 @@
</div>
<div class="vertical-spacer"></div>
<div class="flex-row inner-container">
<div class="contents-view" id="nav-contents-view"></div>
<div class="contents-view" id="nav-contents-view">
<div class="nav-notifications" id="nav-notifications">
</div>
</div>
<div class="edit-file-contents nav-hidden" id="nav-edit-contents-view">
<div class="editor-header" id="nav-edit-contents-header"></div>
<div class="vertical-spacer"></div>

View File

@ -837,6 +837,180 @@ class NavContextMenu {
}
}
class FileUpload {
/**
*
* @param {File|Blob} file
* @param {Number} chunk_size
* @param {NavWindow} nav_window_ref
*/
constructor(file, chunk_size, nav_window_ref) {
this.chunk_size = chunk_size;
this.filename = file.name;
this.nav_window_ref = nav_window_ref;
this.path = nav_window_ref.pwd().path_str() + "/" + file.name;
this.reader = new FileReader();
this.chunks = this.slice_file(file);
this.chunk_index = 0;
}
make_html_element() {
var notification = document.createElement("div");
notification.classList.add("nav-notification");
var header = document.createElement("div");
header.classList.add("nav-notification-header");
notification.appendChild(header);
header.innerText = "Uploading " + this.filename;
var progress = document.createElement("progress");
progress.max = this.num_chunks;
notification.appendChild(progress);
this.progress = progress;
this.html_elements = [progress, header, notification];
document.getElementById("nav-notifications").appendChild(notification);
}
remove_html_element() {
for (let elem of this.html_elements) {
elem.parentElement.removeChild(elem);
}
}
/**
*
* @param {File|Blob} file
* @returns {Array}
*/
slice_file(file) {
var offset = 0;
var chunks = [];
this.num_chunks = Math.ceil(file.size / this.chunk_size);
for (let i = 0; i < this.num_chunks; i++) {
var next_offset = Math.min(this.chunk_size * (i + 1), file.size);
chunks.push(file.slice(offset, next_offset));
offset = next_offset;
}
return chunks;
}
upload() {
this.make_html_element();
this.proc = cockpit.spawn(["/usr/share/cockpit/navigator/scripts/write-chunk.py", this.path], {err: "out", superuser: "try"});
this.proc.fail((e, data) => {
window.alert(data);
})
this.proc.done((data) => {
this.nav_window_ref.stop_load();
})
this.reader.onload = (function(uploader_ref) {
return async function(evt) {
uploader_ref.write_to_file(evt, uploader_ref.chunk_index * uploader_ref.chunk_size);
uploader_ref.chunk_index++;
uploader_ref.progress.value = uploader_ref.chunk_index;
if (uploader_ref.chunk_index < uploader_ref.num_chunks)
uploader_ref.reader.readAsArrayBuffer(uploader_ref.chunks[uploader_ref.chunk_index]);
else {
uploader_ref.done();
}
};
})(this);
this.reader.readAsArrayBuffer(this.chunks[0]);
}
arrayBufferToBase64(buffer) {
let binary = '';
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
/**
*
* @param {Event} evt
* @param {Number} offset
*/
write_to_file(evt, offset) {
var chunk_b64 = this.arrayBufferToBase64(evt.target.result);
const seek = this.chunk_index * this.chunk_size;
var obj = {
seek: seek,
chunk: chunk_b64
};
this.proc.input(JSON.stringify(obj) + "\n", true);
}
done() {
this.proc.input(); // close stdin
this.nav_window_ref.refresh();
this.remove_html_element();
}
}
class NavDragDrop {
/**
*
* @param {HTMLDivElement} drop_area
* @param {NavWindow} nav_window_ref
*/
constructor(drop_area, nav_window_ref) {
drop_area.addEventListener("dragenter", this);
drop_area.addEventListener("dragover", this);
drop_area.addEventListener("dragleave", this);
drop_area.addEventListener("drop", this);
this.drop_area = drop_area;
this.nav_window_ref = nav_window_ref;
}
handleEvent(e) {
e.preventDefault();
switch(e.type){
case "dragenter":
this.drop_area.classList.add("drag-enter");
break;
case "dragover":
break;
case "dragleave":
this.drop_area.classList.remove("drag-enter");
break;
case "drop":
if (e.dataTransfer.items) {
for (let item of e.dataTransfer.items) {
if (item.kind === 'file') {
var file = item.getAsFile();
var uploader = new FileUpload(file, 4096, this.nav_window_ref);
uploader.upload();
// reader.onloadend = (function(file_, nav_window_ref) {
// return function(evt) {
// nav_window_ref.upload(evt, file_);
// };
// })(file, this.nav_window_ref);
// reader.readAsArrayBuffer(file);
}
}
} else {
for (let file of ev.dataTransfer.files) {
var uploader = new FileUpload(file, 4096, this.nav_window_ref);
uploader.upload();
// reader.onloadend = (function(file_, nav_window_ref) {
// return function(evt) {
// nav_window_ref.upload(evt, file_);
// };
// })(file, this.nav_window_ref);
// reader.readAsArrayBuffer(file);
}
}
this.drop_area.classList.remove("drag-enter");
break;
default:
this.drop_area.classList.remove("drag-enter");
break;
}
e.stopPropagation();
}
}
class NavWindow {
constructor() {
this.path_stack = (localStorage.getItem('navigator-path') ?? '/').split('/');
@ -853,6 +1027,8 @@ class NavWindow {
this.context_menu = new NavContextMenu("nav-context-menu", this);
this.clip_board = [];
this.uploader = new NavDragDrop(this.window, this);
}
/**
@ -1386,6 +1562,27 @@ class NavWindow {
}
document.getElementById("pwd").disabled = false;
}
// /**
// *
// * @param {Event} event
// * @param {*} file
// */
// async upload(event, file) {
// var bytes = new Uint8Array(event.target.result);
// console.log(bytes, file);
// var out_path = this.pwd().path_str() + "/" + file.name;
// var proc = cockpit.spawn(["dd", "of=" + out_path], {superuser: "try", err: "out"});
// // for (let i = 0; i < bytes.byteLength; i++) {
// // proc.input(bytes[i], true);
// // }
// proc.input(bytes);
// proc.fail((e, data) => {
// window.alert(data);
// });
// await proc;
// this.refresh();
// }
}
let nav_window = new NavWindow();

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Cockpit Navigator - A File System Browser for Cockpit.
Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
This file is part of Cockpit Navigator.
Cockpit Navigator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Cockpit Navigator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
"""
import base64
import os
import sys
import json
def write_chunk(chunk, file):
seek = chunk["seek"]
data = base64.b64decode(chunk["chunk"])
file.seek(seek)
file.write(data)
def main():
if len(sys.argv) != 2:
print("Invalid number of arguments.")
sys.exit(1)
path = sys.argv[1]
try:
file = open(path, "a+b")
except Exception as e:
print(e)
sys.exit(1)
while True:
try:
json_in = input()
except EOFError:
break
json_list = json_in.split("\n")
for json_obj in json_list:
try:
obj_in = json.loads(json_obj)
except Exception as e:
print(e)
log = open("/var/log/navigator.log", "w")
log.write(json_in)
log.close()
sys.exit(1)
write_chunk(obj_in, file)
file.close()
sys.exit(0)
if __name__ == "__main__":
main()