2022-06-21 20:20:34 -04:00
|
|
|
let new_directory_name = document.getElementById("name");
|
|
|
|
let files = document.getElementById("files");
|
2022-08-16 17:40:32 -04:00
|
|
|
let dirs = document.getElementById("dirs");
|
2022-06-21 20:20:34 -04:00
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
var url_base = window.location;
|
|
|
|
var current_path;
|
2022-06-24 18:15:42 -04:00
|
|
|
var editable = undefined;
|
2022-06-21 20:20:34 -04:00
|
|
|
|
2022-08-16 16:51:40 -04:00
|
|
|
function compareValues(a, b) {
|
|
|
|
if (a.directory == b.directory && a.name.toLowerCase() === b.name.toLowerCase()) {
|
|
|
|
return 0;
|
|
|
|
} else if (a.directory != b.directory) {
|
|
|
|
return a.directory < b.directory ? 1 : -1;
|
|
|
|
} else {
|
|
|
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-17 14:31:11 -04:00
|
|
|
function set_upload_enabled(enabled) {
|
|
|
|
files.disabled = !enabled;
|
|
|
|
dirs.disabled = !enabled;
|
|
|
|
}
|
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
async function refresh_list() {
|
|
|
|
current_path = window.location.hash.substr(1);
|
|
|
|
if (current_path == "") {
|
|
|
|
current_path = "/";
|
|
|
|
}
|
2022-06-24 18:15:42 -04:00
|
|
|
// Do the fetch first because the browser will prompt for credentials.
|
2022-06-22 19:44:41 -04:00
|
|
|
const response = await fetch(new URL("/fs" + current_path, url_base),
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
"Accept": "application/json"
|
|
|
|
},
|
|
|
|
credentials: "include"
|
|
|
|
}
|
|
|
|
);
|
|
|
|
const data = await response.json();
|
|
|
|
var new_children = [];
|
2022-06-24 18:15:42 -04:00
|
|
|
var title = document.querySelector("title");
|
|
|
|
title.textContent = current_path;
|
|
|
|
var path = document.querySelector("#path");
|
|
|
|
path.textContent = current_path;
|
2022-06-22 19:44:41 -04:00
|
|
|
var template = document.querySelector('#row');
|
|
|
|
|
2022-06-24 18:15:42 -04:00
|
|
|
if (editable === undefined) {
|
|
|
|
const status = await fetch(new URL("/fs/", url_base),
|
|
|
|
{
|
|
|
|
method: "OPTIONS",
|
|
|
|
credentials: "include"
|
|
|
|
}
|
|
|
|
);
|
|
|
|
editable = status.headers.get("Access-Control-Allow-Methods").includes("DELETE");
|
|
|
|
new_directory_name.disabled = !editable;
|
2022-08-17 14:31:11 -04:00
|
|
|
set_upload_enabled(editable);
|
2022-06-30 19:54:06 -04:00
|
|
|
if (!editable) {
|
|
|
|
let usbwarning = document.querySelector("#usbwarning");
|
|
|
|
usbwarning.style.display = "block";
|
|
|
|
}
|
2022-06-24 18:15:42 -04:00
|
|
|
}
|
|
|
|
|
2022-08-16 16:51:40 -04:00
|
|
|
if (current_path != "/") {
|
2022-06-22 19:44:41 -04:00
|
|
|
var clone = template.content.cloneNode(true);
|
|
|
|
var td = clone.querySelectorAll("td");
|
2022-08-04 12:39:21 -04:00
|
|
|
td[0].textContent = "📁";
|
2022-06-22 19:44:41 -04:00
|
|
|
var path = clone.querySelector("a");
|
|
|
|
let parent = new URL("..", "file://" + current_path);
|
|
|
|
path.href = "#" + parent.pathname;
|
|
|
|
path.textContent = "..";
|
|
|
|
// Remove the delete button
|
|
|
|
td[4].replaceChildren();
|
2022-08-16 16:51:40 -04:00
|
|
|
td[5].replaceChildren();
|
|
|
|
td[6].replaceChildren();
|
2022-06-22 19:44:41 -04:00
|
|
|
new_children.push(clone);
|
|
|
|
}
|
|
|
|
|
2022-08-09 17:13:18 -04:00
|
|
|
data.sort(compareValues);
|
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
for (const f of data) {
|
|
|
|
// Clone the new row and insert it into the table
|
|
|
|
var clone = template.content.cloneNode(true);
|
|
|
|
var td = clone.querySelectorAll("td");
|
2022-08-04 12:39:21 -04:00
|
|
|
var icon = "⬇️";
|
2022-06-22 19:44:41 -04:00
|
|
|
var file_path = current_path + f.name;
|
|
|
|
let api_url = new URL("/fs" + file_path, url_base);
|
2022-08-01 22:56:11 -04:00
|
|
|
let edit_url = "/edit/#" + file_path;
|
2022-06-22 19:44:41 -04:00
|
|
|
if (f.directory) {
|
|
|
|
file_path = "#" + file_path + "/";
|
|
|
|
api_url += "/";
|
|
|
|
} else {
|
|
|
|
file_path = api_url;
|
|
|
|
}
|
|
|
|
|
2022-08-16 16:51:40 -04:00
|
|
|
var text_file = false;
|
2022-06-22 19:44:41 -04:00
|
|
|
if (f.directory) {
|
2022-08-04 12:39:21 -04:00
|
|
|
icon = "📁";
|
2022-06-22 19:44:41 -04:00
|
|
|
} else if(f.name.endsWith(".txt") ||
|
2022-08-16 19:02:55 -04:00
|
|
|
f.name.endsWith(".env") ||
|
2022-06-22 19:44:41 -04:00
|
|
|
f.name.endsWith(".py") ||
|
|
|
|
f.name.endsWith(".js") ||
|
|
|
|
f.name.endsWith(".json")) {
|
2022-08-04 12:39:21 -04:00
|
|
|
icon = "📄";
|
2022-08-16 16:51:40 -04:00
|
|
|
text_file = true;
|
2022-06-22 19:44:41 -04:00
|
|
|
} else if (f.name.endsWith(".html")) {
|
|
|
|
icon = "🌐";
|
2022-08-16 16:51:40 -04:00
|
|
|
text_file = true;
|
2022-06-22 19:44:41 -04:00
|
|
|
}
|
|
|
|
td[0].textContent = icon;
|
|
|
|
td[1].textContent = f.file_size;
|
2022-08-16 16:51:40 -04:00
|
|
|
var path = clone.querySelector("a.path");
|
2022-06-22 19:44:41 -04:00
|
|
|
path.href = file_path;
|
|
|
|
path.textContent = f.name;
|
2022-08-16 16:51:40 -04:00
|
|
|
let modtime = clone.querySelector("td.modtime");
|
|
|
|
modtime.textContent = (new Date(f.modified_ns / 1000000)).toLocaleString();
|
2022-06-22 19:44:41 -04:00
|
|
|
var delete_button = clone.querySelector("button.delete");
|
|
|
|
delete_button.value = api_url;
|
2022-06-24 18:15:42 -04:00
|
|
|
delete_button.disabled = !editable;
|
2022-06-22 19:44:41 -04:00
|
|
|
delete_button.onclick = del;
|
|
|
|
|
2022-08-16 16:51:40 -04:00
|
|
|
|
|
|
|
var rename_button = clone.querySelector("button.rename");
|
|
|
|
rename_button.value = api_url;
|
|
|
|
rename_button.disabled = !editable;
|
|
|
|
rename_button.onclick = rename;
|
|
|
|
|
|
|
|
let edit_link = clone.querySelector(".edit_link");
|
|
|
|
if (text_file && editable && !f.directory) {
|
2022-08-03 17:56:43 -04:00
|
|
|
edit_url = new URL(edit_url, url_base);
|
|
|
|
edit_link.href = edit_url
|
2022-08-16 16:51:40 -04:00
|
|
|
} else if (f.directory) {
|
|
|
|
edit_link.style = "display: none;";
|
|
|
|
} else {
|
|
|
|
edit_link.querySelector("button").disabled = true;
|
2022-08-03 17:56:43 -04:00
|
|
|
}
|
2022-06-24 18:15:42 -04:00
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
new_children.push(clone);
|
|
|
|
}
|
|
|
|
var tbody = document.querySelector("tbody");
|
|
|
|
tbody.replaceChildren(...new_children);
|
2022-06-21 20:02:01 -04:00
|
|
|
}
|
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
async function find_devices() {
|
2022-06-23 19:32:42 -04:00
|
|
|
const version_response = await fetch("/cp/version.json");
|
|
|
|
if (version_response.ok) {
|
|
|
|
url_base = new URL("/", window.location).href;
|
|
|
|
} else {
|
|
|
|
// TODO: Remove this when we've settled things. It is only used when this file isn't hosted
|
|
|
|
// by a CP device.
|
|
|
|
const response = await fetch("http://circuitpython.local/cp/devices.json");
|
|
|
|
let url = new URL("/", response.url);
|
|
|
|
url_base = url.href;
|
|
|
|
const data = await response.json();
|
|
|
|
}
|
2022-06-22 19:44:41 -04:00
|
|
|
refresh_list();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function mkdir(e) {
|
|
|
|
const response = await fetch(
|
|
|
|
new URL("/fs" + current_path + new_directory_name.value + "/", url_base),
|
|
|
|
{
|
|
|
|
method: "PUT",
|
|
|
|
headers: {
|
|
|
|
'X-Timestamp': Date.now()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
if (response.ok) {
|
|
|
|
refresh_list();
|
|
|
|
new_directory_name.value = "";
|
|
|
|
mkdir_button.disabled = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function upload(e) {
|
2022-08-17 14:31:11 -04:00
|
|
|
set_upload_enabled(false);
|
2022-08-16 20:03:09 -04:00
|
|
|
let progress = document.querySelector("progress");
|
2022-08-16 17:40:32 -04:00
|
|
|
let made_dirs = new Set();
|
2022-08-16 20:03:09 -04:00
|
|
|
progress.max = files.files.length + dirs.files.length;
|
|
|
|
progress.value = 0;
|
2022-08-16 17:40:32 -04:00
|
|
|
for (const file of [...files.files, ...dirs.files]) {
|
|
|
|
let file_name = file.name;
|
|
|
|
if (file.webkitRelativePath) {
|
|
|
|
file_name = file.webkitRelativePath;
|
|
|
|
let components = file_name.split("/");
|
|
|
|
components.pop();
|
|
|
|
let parent_dir = components.join("/");
|
|
|
|
if (!made_dirs.has(parent_dir)) {
|
|
|
|
new_directory_name.value = parent_dir;
|
|
|
|
await mkdir(null);
|
|
|
|
made_dirs.add(parent_dir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let file_path = new URL("/fs" + current_path + file_name, url_base);
|
2022-06-22 19:44:41 -04:00
|
|
|
const response = await fetch(file_path,
|
|
|
|
{
|
|
|
|
method: "PUT",
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/octet-stream',
|
|
|
|
'X-Timestamp': file.lastModified
|
|
|
|
},
|
|
|
|
body: file
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if (response.ok) {
|
|
|
|
refresh_list();
|
|
|
|
}
|
2022-08-16 20:03:09 -04:00
|
|
|
progress.value += 1;
|
2022-06-22 19:44:41 -04:00
|
|
|
}
|
2022-08-09 19:36:57 -04:00
|
|
|
files.value = "";
|
2022-08-16 17:40:32 -04:00
|
|
|
dirs.value = "";
|
2022-08-16 20:03:09 -04:00
|
|
|
progress.value = 0;
|
2022-08-17 14:31:11 -04:00
|
|
|
set_upload_enabled(true);
|
2022-06-21 20:02:01 -04:00
|
|
|
}
|
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
async function del(e) {
|
|
|
|
let fn = new URL(e.target.value);
|
|
|
|
var prompt = "Delete " + fn.pathname.substr(3);
|
|
|
|
if (e.target.value.endsWith("/")) {
|
|
|
|
prompt += " and all of its contents?";
|
|
|
|
} else {
|
|
|
|
prompt += "?";
|
|
|
|
}
|
|
|
|
if (confirm(prompt)) {
|
|
|
|
const response = await fetch(e.target.value,
|
|
|
|
{
|
|
|
|
method: "DELETE"
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if (response.ok) {
|
|
|
|
refresh_list();
|
|
|
|
}
|
2022-06-21 20:02:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-16 16:51:40 -04:00
|
|
|
async function rename(e) {
|
|
|
|
let fn = new URL(e.target.value);
|
|
|
|
var new_fn = prompt("Rename to ", fn.pathname.substr(3));
|
2022-08-16 19:02:55 -04:00
|
|
|
if (new_fn === null) {
|
|
|
|
return;
|
|
|
|
}
|
2022-08-16 16:51:40 -04:00
|
|
|
let new_uri = new URL("/fs" + new_fn, fn);
|
|
|
|
const response = await fetch(e.target.value,
|
|
|
|
{
|
|
|
|
method: "MOVE",
|
|
|
|
headers: {
|
|
|
|
'X-Destination': new_uri.pathname,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
if (response.ok) {
|
|
|
|
refresh_list();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
find_devices();
|
|
|
|
|
2022-06-21 20:20:34 -04:00
|
|
|
let mkdir_button = document.getElementById("mkdir");
|
|
|
|
mkdir_button.onclick = mkdir;
|
|
|
|
|
2022-08-17 14:31:11 -04:00
|
|
|
files.onchange = upload;
|
|
|
|
dirs.onchange = upload;
|
2022-06-21 20:20:34 -04:00
|
|
|
|
|
|
|
mkdir_button.disabled = new_directory_name.value.length == 0;
|
|
|
|
|
|
|
|
new_directory_name.oninput = () => {
|
|
|
|
mkdir_button.disabled = new_directory_name.value.length == 0;
|
|
|
|
}
|
2022-06-21 20:02:01 -04:00
|
|
|
|
2022-06-22 19:44:41 -04:00
|
|
|
window.onhashchange = refresh_list;
|