mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-30 17:15:16 +02:00
implement drag and drop file upload
This commit is contained in:
parent
9800280ec2
commit
ee6fc26ffc
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
61
navigator/scripts/write-chunk.py
Executable file
61
navigator/scripts/write-chunk.py
Executable 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()
|
Loading…
x
Reference in New Issue
Block a user