From f3db5709c3a91fc542fbc05c26c34f3a90a86956 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Wed, 3 Aug 2022 16:33:13 -0400 Subject: [PATCH 01/13] Add filename sort to web workflow file manager --- .../shared/web_workflow/static/directory.html | 4 +- .../shared/web_workflow/static/directory.js | 169 ++++++++++++++++++ 2 files changed, 171 insertions(+), 2 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index b88e74684b..6e2b833d6f 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -11,8 +11,8 @@

 

- - +
TypeSizePathModified
+
TypeSizeModified

diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 91389343d6..657a8169d4 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -1,3 +1,172 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: sortable-table.js + * + * Desc: Adds sorting to a HTML data table that implements ARIA Authoring Practices + */ + +'use strict'; + +class SortableTable { + constructor(tableNode) { + this.tableNode = tableNode; + + this.columnHeaders = tableNode.querySelectorAll('thead th'); + + this.sortColumns = []; + + for (var i = 0; i < this.columnHeaders.length; i++) { + var ch = this.columnHeaders[i]; + var buttonNode = ch.querySelector('button'); + if (buttonNode) { + this.sortColumns.push(i); + buttonNode.setAttribute('data-column-index', i); + buttonNode.addEventListener('click', this.handleClick.bind(this)); + } + } + + this.optionCheckbox = document.querySelector( + 'input[type="checkbox"][value="show-unsorted-icon"]' + ); + + if (this.optionCheckbox) { + this.optionCheckbox.addEventListener( + 'change', + this.handleOptionChange.bind(this) + ); + if (this.optionCheckbox.checked) { + this.tableNode.classList.add('show-unsorted-icon'); + } + } + } + + setColumnHeaderSort(columnIndex) { + if (typeof columnIndex === 'string') { + columnIndex = parseInt(columnIndex); + } + + for (var i = 0; i < this.columnHeaders.length; i++) { + var ch = this.columnHeaders[i]; + var buttonNode = ch.querySelector('button'); + if (i === columnIndex) { + var value = ch.getAttribute('aria-sort'); + if (value === 'descending') { + ch.setAttribute('aria-sort', 'ascending'); + this.sortColumn( + columnIndex, + 'ascending', + ch.classList.contains('num') + ); + } else { + ch.setAttribute('aria-sort', 'descending'); + this.sortColumn( + columnIndex, + 'descending', + ch.classList.contains('num') + ); + } + } else { + if (ch.hasAttribute('aria-sort') && buttonNode) { + ch.removeAttribute('aria-sort'); + } + } + } + } + + sortColumn(columnIndex, sortValue, isNumber) { + function compareValues(a, b) { + if (sortValue === 'ascending') { + if (a.value === b.value) { + return 0; + } else { + if (isNumber) { + return a.value - b.value; + } else { + return a.value < b.value ? -1 : 1; + } + } + } else { + if (a.value === b.value) { + return 0; + } else { + if (isNumber) { + return b.value - a.value; + } else { + return a.value > b.value ? -1 : 1; + } + } + } + } + + if (typeof isNumber !== 'boolean') { + isNumber = false; + } + + var tbodyNode = this.tableNode.querySelector('tbody'); + var rowNodes = []; + var dataCells = []; + + var rowNode = tbodyNode.firstElementChild; + + var index = 0; + while (rowNode) { + rowNodes.push(rowNode); + var rowCells = rowNode.querySelectorAll('th, td'); + var dataCell = rowCells[columnIndex]; + + var data = {}; + data.index = index; + data.value = dataCell.textContent.toLowerCase().trim(); + if (isNumber) { + data.value = parseFloat(data.value); + } + dataCells.push(data); + rowNode = rowNode.nextElementSibling; + index += 1; + } + + dataCells.sort(compareValues); + + // remove rows + while (tbodyNode.firstChild) { + tbodyNode.removeChild(tbodyNode.lastChild); + } + + // add sorted rows + for (var i = 0; i < dataCells.length; i += 1) { + tbodyNode.appendChild(rowNodes[dataCells[i].index]); + } + } + + /* EVENT HANDLERS */ + + handleClick(event) { + var tgt = event.currentTarget; + this.setColumnHeaderSort(tgt.getAttribute('data-column-index')); + } + + handleOptionChange(event) { + var tgt = event.currentTarget; + + if (tgt.checked) { + this.tableNode.classList.add('show-unsorted-icon'); + } else { + this.tableNode.classList.remove('show-unsorted-icon'); + } + } +} + +// Initialize sortable table buttons +window.addEventListener('load', function () { + var sortableTables = document.querySelectorAll('table.sortable'); + for (var i = 0; i < sortableTables.length; i++) { + new SortableTable(sortableTables[i]); + } +}); + + let new_directory_name = document.getElementById("name"); let files = document.getElementById("files"); From 9e2f6dfc13c1f368cf07fb17ec9066f8ce84ea37 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Wed, 3 Aug 2022 17:56:43 -0400 Subject: [PATCH 02/13] Turn off edit links when not editable --- supervisor/shared/web_workflow/static/directory.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 657a8169d4..34aa1e7f70 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -261,9 +261,11 @@ async function refresh_list() { delete_button.disabled = !editable; delete_button.onclick = del; - edit_url = new URL(edit_url, url_base); - let edit_link = clone.querySelector(".edit_link"); - edit_link.href = edit_url + if (editable) { + edit_url = new URL(edit_url, url_base); + let edit_link = clone.querySelector(".edit_link"); + edit_link.href = edit_url + } new_children.push(clone); } From 98fbaa8ff9cadd60ce2a4626318d16aeeed059db Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 00:58:57 -0400 Subject: [PATCH 03/13] Sorts ascending without mouse click --- .../shared/web_workflow/static/directory.html | 4 +- .../shared/web_workflow/static/directory.js | 118 +++--------------- 2 files changed, 17 insertions(+), 105 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index 6e2b833d6f..a1b4dd9f4c 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -10,9 +10,9 @@

 

- + - +
TypeSizeModified
TypeSizePathModified

diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 34aa1e7f70..a9da6a0fb0 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -7,101 +7,30 @@ * Desc: Adds sorting to a HTML data table that implements ARIA Authoring Practices */ -'use strict'; +// 'use strict'; + +var columnIndex = 2; +const sortEvent = new Event('sort'); class SortableTable { constructor(tableNode) { this.tableNode = tableNode; this.columnHeaders = tableNode.querySelectorAll('thead th'); - - this.sortColumns = []; - - for (var i = 0; i < this.columnHeaders.length; i++) { - var ch = this.columnHeaders[i]; - var buttonNode = ch.querySelector('button'); - if (buttonNode) { - this.sortColumns.push(i); - buttonNode.setAttribute('data-column-index', i); - buttonNode.addEventListener('click', this.handleClick.bind(this)); - } - } - - this.optionCheckbox = document.querySelector( - 'input[type="checkbox"][value="show-unsorted-icon"]' - ); - - if (this.optionCheckbox) { - this.optionCheckbox.addEventListener( - 'change', - this.handleOptionChange.bind(this) - ); - if (this.optionCheckbox.checked) { - this.tableNode.classList.add('show-unsorted-icon'); - } - } } setColumnHeaderSort(columnIndex) { - if (typeof columnIndex === 'string') { - columnIndex = parseInt(columnIndex); - } - - for (var i = 0; i < this.columnHeaders.length; i++) { - var ch = this.columnHeaders[i]; - var buttonNode = ch.querySelector('button'); - if (i === columnIndex) { - var value = ch.getAttribute('aria-sort'); - if (value === 'descending') { - ch.setAttribute('aria-sort', 'ascending'); - this.sortColumn( - columnIndex, - 'ascending', - ch.classList.contains('num') - ); - } else { - ch.setAttribute('aria-sort', 'descending'); - this.sortColumn( - columnIndex, - 'descending', - ch.classList.contains('num') - ); - } - } else { - if (ch.hasAttribute('aria-sort') && buttonNode) { - ch.removeAttribute('aria-sort'); - } - } - } + var ch = this.columnHeaders[columnIndex]; + this.sortColumn(columnIndex); } - sortColumn(columnIndex, sortValue, isNumber) { + sortColumn(columnIndex) { function compareValues(a, b) { - if (sortValue === 'ascending') { if (a.value === b.value) { return 0; } else { - if (isNumber) { - return a.value - b.value; - } else { - return a.value < b.value ? -1 : 1; - } + return a.value < b.value ? -1 : 1; } - } else { - if (a.value === b.value) { - return 0; - } else { - if (isNumber) { - return b.value - a.value; - } else { - return a.value > b.value ? -1 : 1; - } - } - } - } - - if (typeof isNumber !== 'boolean') { - isNumber = false; } var tbodyNode = this.tableNode.querySelector('tbody'); @@ -119,9 +48,6 @@ class SortableTable { var data = {}; data.index = index; data.value = dataCell.textContent.toLowerCase().trim(); - if (isNumber) { - data.value = parseFloat(data.value); - } dataCells.push(data); rowNode = rowNode.nextElementSibling; index += 1; @@ -142,30 +68,14 @@ class SortableTable { /* EVENT HANDLERS */ - handleClick(event) { - var tgt = event.currentTarget; - this.setColumnHeaderSort(tgt.getAttribute('data-column-index')); - } - - handleOptionChange(event) { - var tgt = event.currentTarget; - - if (tgt.checked) { - this.tableNode.classList.add('show-unsorted-icon'); - } else { - this.tableNode.classList.remove('show-unsorted-icon'); - } + handleSort(event) { + this.setColumnHeaderSort(tgt.getAttribute(columnIndex)); } } -// Initialize sortable table buttons -window.addEventListener('load', function () { - var sortableTables = document.querySelectorAll('table.sortable'); - for (var i = 0; i < sortableTables.length; i++) { - new SortableTable(sortableTables[i]); - } -}); - +var sortable_directory = document.querySelector('table.sortable'); +const sd_class = {sortable_dir: new SortableTable(sortable_directory)}; +sortable_directory.addEventListener('sort', function () { sd_class["sortable_dir"].setColumnHeaderSort(columnIndex); } ); let new_directory_name = document.getElementById("name"); let files = document.getElementById("files"); @@ -271,6 +181,8 @@ async function refresh_list() { } var tbody = document.querySelector("tbody"); tbody.replaceChildren(...new_children); + + sortable_directory.dispatchEvent(sortEvent); } async function find_devices() { From 9eb32f4dd1e832d6893c5a4a1cde86f3438d2384 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 01:23:01 -0400 Subject: [PATCH 04/13] Ascending sort without mouse click --- supervisor/shared/web_workflow/static/directory.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index a1b4dd9f4c..7328cba78a 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -10,7 +10,7 @@

 

- + From 85d959b953c1487e739c277118008063f98de6fe Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 01:42:59 -0400 Subject: [PATCH 05/13] A little bit more cleanup.... --- supervisor/shared/web_workflow/static/directory.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index a9da6a0fb0..7b6e1c781c 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -15,16 +15,9 @@ const sortEvent = new Event('sort'); class SortableTable { constructor(tableNode) { this.tableNode = tableNode; - - this.columnHeaders = tableNode.querySelectorAll('thead th'); } setColumnHeaderSort(columnIndex) { - var ch = this.columnHeaders[columnIndex]; - this.sortColumn(columnIndex); - } - - sortColumn(columnIndex) { function compareValues(a, b) { if (a.value === b.value) { return 0; From ed6f9eac04b77d98f46e49797624c83371efb76e Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 02:06:45 -0400 Subject: [PATCH 06/13] Unnecessary handler removed --- supervisor/shared/web_workflow/static/directory.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 7b6e1c781c..3a65b5694e 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -58,12 +58,6 @@ class SortableTable { tbodyNode.appendChild(rowNodes[dataCells[i].index]); } } - - /* EVENT HANDLERS */ - - handleSort(event) { - this.setColumnHeaderSort(tgt.getAttribute(columnIndex)); - } } var sortable_directory = document.querySelector('table.sortable'); From b7d3ee174abffb9bda2b7438ea7c663113baee83 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 21:49:18 -0400 Subject: [PATCH 07/13] Move sort from table class to async function --- .../shared/web_workflow/static/directory.html | 2 +- .../shared/web_workflow/static/directory.js | 127 ++++++++---------- 2 files changed, 59 insertions(+), 70 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index 7328cba78a..b88e74684b 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -11,7 +11,7 @@

 

-
TypeSizePathModified
+
TypeSizePathModified
diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 3a65b5694e..9158bfbc87 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -1,68 +1,6 @@ -/* - * This content is licensed according to the W3C Software License at - * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document - * - * File: sortable-table.js - * - * Desc: Adds sorting to a HTML data table that implements ARIA Authoring Practices - */ - -// 'use strict'; - -var columnIndex = 2; -const sortEvent = new Event('sort'); - -class SortableTable { - constructor(tableNode) { - this.tableNode = tableNode; - } - - setColumnHeaderSort(columnIndex) { - function compareValues(a, b) { - if (a.value === b.value) { - return 0; - } else { - return a.value < b.value ? -1 : 1; - } - } - - var tbodyNode = this.tableNode.querySelector('tbody'); - var rowNodes = []; - var dataCells = []; - - var rowNode = tbodyNode.firstElementChild; - - var index = 0; - while (rowNode) { - rowNodes.push(rowNode); - var rowCells = rowNode.querySelectorAll('th, td'); - var dataCell = rowCells[columnIndex]; - - var data = {}; - data.index = index; - data.value = dataCell.textContent.toLowerCase().trim(); - dataCells.push(data); - rowNode = rowNode.nextElementSibling; - index += 1; - } - - dataCells.sort(compareValues); - - // remove rows - while (tbodyNode.firstChild) { - tbodyNode.removeChild(tbodyNode.lastChild); - } - - // add sorted rows - for (var i = 0; i < dataCells.length; i += 1) { - tbodyNode.appendChild(rowNodes[dataCells[i].index]); - } - } -} - -var sortable_directory = document.querySelector('table.sortable'); -const sd_class = {sortable_dir: new SortableTable(sortable_directory)}; -sortable_directory.addEventListener('sort', function () { sd_class["sortable_dir"].setColumnHeaderSort(columnIndex); } ); +// var sort_column = undefined; +// (document.querySelectorAll("th")).forEach((element, indx) => { if (element.textContent == "Path") {sort_column = indx} } ); +var sort_column = 2; let new_directory_name = document.getElementById("name"); let files = document.getElementById("files"); @@ -72,6 +10,15 @@ var current_path; var editable = undefined; async function refresh_list() { + + function compareValues(a, b) { + if (a.value === b.value) { + return 0; + } else { + return a.value < b.value ? -1 : 1; + } + } + current_path = window.location.hash.substr(1); if (current_path == "") { current_path = "/"; @@ -109,6 +56,10 @@ async function refresh_list() { } } + var dirCells = []; + var dataCells = []; + var index = 0; + if (window.location.path != "/fs/") { var clone = template.content.cloneNode(true); var td = clone.querySelectorAll("td"); @@ -119,6 +70,15 @@ async function refresh_list() { path.textContent = ".."; // Remove the delete button td[4].replaceChildren(); + + var dataCell = td[sort_column]; + + var sortdata = {}; + sortdata.value = dataCell.textContent.toLowerCase().trim(); + sortdata.index = index; + dirCells.push(sortdata); + index += 1; + new_children.push(clone); } @@ -126,6 +86,8 @@ async function refresh_list() { // Clone the new row and insert it into the table var clone = template.content.cloneNode(true); var td = clone.querySelectorAll("td"); + var dataCell = td[sort_column]; + var icon = "⬇"; var file_path = current_path + f.name; let api_url = new URL("/fs" + file_path, url_base); @@ -158,18 +120,45 @@ async function refresh_list() { delete_button.disabled = !editable; delete_button.onclick = del; - if (editable) { + if (editable && !f.directory) { edit_url = new URL(edit_url, url_base); let edit_link = clone.querySelector(".edit_link"); edit_link.href = edit_url } + var dataCell = td[sort_column]; + + var sortdata = {}; + sortdata.value = dataCell.textContent.toLowerCase().trim(); + sortdata.index = index; + if (!f.directory) { + dataCells.push(sortdata); + index += 1; + } else { + dirCells.push(sortdata); + index += 1; + } + new_children.push(clone); } - var tbody = document.querySelector("tbody"); - tbody.replaceChildren(...new_children); - sortable_directory.dispatchEvent(sortEvent); + dirCells.sort(compareValues); + dataCells.sort(compareValues); + + var tbody = document.querySelector("tbody"); + + // remove rows + while (tbody.firstChild) { + tbody.removeChild(tbody.lastChild); + } + + // add sorted rows + for (var i = 0; i < dirCells.length; i += 1) { + tbody.appendChild(new_children[dirCells[i].index]); + } + for (var i = 0; i < dataCells.length; i += 1) { + tbody.appendChild(new_children[dataCells[i].index]); + } } async function find_devices() { From 02167898f7a1c85fb7d0787d7bdbe5eea92577a4 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 22:05:32 -0400 Subject: [PATCH 08/13] minor cleanup --- directory.js | 253 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 directory.js diff --git a/directory.js b/directory.js new file mode 100644 index 0000000000..a822d4a239 --- /dev/null +++ b/directory.js @@ -0,0 +1,253 @@ +// var sort_column = undefined; +// (document.querySelectorAll("th")).forEach((element, indx) => { if (element.textContent == "Path") {sort_column = indx} } ); +var sort_column = 2; + +let new_directory_name = document.getElementById("name"); +let files = document.getElementById("files"); + +var url_base = window.location; +var current_path; +var editable = undefined; + +async function refresh_list() { + + function compareValues(a, b) { + if (a.value === b.value) { + return 0; + } else { + return a.value < b.value ? -1 : 1; + } + } + + current_path = window.location.hash.substr(1); + if (current_path == "") { + current_path = "/"; + } + // Do the fetch first because the browser will prompt for credentials. + 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 = []; + var title = document.querySelector("title"); + title.textContent = current_path; + var path = document.querySelector("#path"); + path.textContent = current_path; + var template = document.querySelector('#row'); + + 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; + files.disabled = !editable; + if (!editable) { + let usbwarning = document.querySelector("#usbwarning"); + usbwarning.style.display = "block"; + } + } + + var dirCells = []; + var dataCells = []; + var index = 0; + + if (window.location.path != "/fs/") { + var clone = template.content.cloneNode(true); + var td = clone.querySelectorAll("td"); + td[0].textContent = "🗀"; + 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(); + + var sortdata = {}; + sortdata.value = ".."; + sortdata.index = index; + dirCells.push(sortdata); + index += 1; + + new_children.push(clone); + } + + 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"); + var icon = "⬇"; + var file_path = current_path + f.name; + let api_url = new URL("/fs" + file_path, url_base); + let edit_url = "/edit/#" + file_path; + if (f.directory) { + file_path = "#" + file_path + "/"; + api_url += "/"; + } else { + file_path = api_url; + } + + if (f.directory) { + icon = "🗀"; + } else if(f.name.endsWith(".txt") || + f.name.endsWith(".py") || + f.name.endsWith(".js") || + f.name.endsWith(".json")) { + icon = "🖹"; + } else if (f.name.endsWith(".html")) { + icon = "🌐"; + } + td[0].textContent = icon; + td[1].textContent = f.file_size; + var path = clone.querySelector("a"); + path.href = file_path; + path.textContent = f.name; + td[3].textContent = (new Date(f.modified_ns / 1000000)).toLocaleString(); + var delete_button = clone.querySelector("button.delete"); + delete_button.value = api_url; + delete_button.disabled = !editable; + delete_button.onclick = del; + + if (editable && !f.directory) { + edit_url = new URL(edit_url, url_base); + let edit_link = clone.querySelector(".edit_link"); + edit_link.href = edit_url + } + + var dataCell = td[sort_column]; + + var sortdata = {}; + sortdata.value = dataCell.textContent.toLowerCase().trim(); + sortdata.index = index; + if (!f.directory) { + dataCells.push(sortdata); + index += 1; + } else { + dirCells.push(sortdata); + index += 1; + } + + new_children.push(clone); + } + + dirCells.sort(compareValues); + dataCells.sort(compareValues); + + var tbody = document.querySelector("tbody"); + + // remove rows + while (tbody.firstChild) { + tbody.removeChild(tbody.lastChild); + } + + // add sorted rows + for (var i = 0; i < dirCells.length; i += 1) { + tbody.appendChild(new_children[dirCells[i].index]); + } + for (var i = 0; i < dataCells.length; i += 1) { + tbody.appendChild(new_children[dataCells[i].index]); + } +} + +async function find_devices() { + 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(); + } + 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) { + for (const file of files.files) { + let file_path = new URL("/fs" + current_path + file.name, url_base); + 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(); + files.value = ""; + upload_button.disabled = true; + } + } +} + +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(); + } + } +} + +find_devices(); + +let mkdir_button = document.getElementById("mkdir"); +mkdir_button.onclick = mkdir; + +let upload_button = document.getElementById("upload"); +upload_button.onclick = upload; + +upload_button.disabled = files.files.length == 0; + +files.onchange = () => { + upload_button.disabled = files.files.length == 0; +} + +mkdir_button.disabled = new_directory_name.value.length == 0; + +new_directory_name.oninput = () => { + mkdir_button.disabled = new_directory_name.value.length == 0; +} + +window.onhashchange = refresh_list; From 52bd028dac555cb0409288dcc9c1afd98f8c9545 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 22:06:52 -0400 Subject: [PATCH 09/13] Posted to wrong directory :/ --- directory.js | 253 --------------------------------------------------- 1 file changed, 253 deletions(-) delete mode 100644 directory.js diff --git a/directory.js b/directory.js deleted file mode 100644 index a822d4a239..0000000000 --- a/directory.js +++ /dev/null @@ -1,253 +0,0 @@ -// var sort_column = undefined; -// (document.querySelectorAll("th")).forEach((element, indx) => { if (element.textContent == "Path") {sort_column = indx} } ); -var sort_column = 2; - -let new_directory_name = document.getElementById("name"); -let files = document.getElementById("files"); - -var url_base = window.location; -var current_path; -var editable = undefined; - -async function refresh_list() { - - function compareValues(a, b) { - if (a.value === b.value) { - return 0; - } else { - return a.value < b.value ? -1 : 1; - } - } - - current_path = window.location.hash.substr(1); - if (current_path == "") { - current_path = "/"; - } - // Do the fetch first because the browser will prompt for credentials. - 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 = []; - var title = document.querySelector("title"); - title.textContent = current_path; - var path = document.querySelector("#path"); - path.textContent = current_path; - var template = document.querySelector('#row'); - - 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; - files.disabled = !editable; - if (!editable) { - let usbwarning = document.querySelector("#usbwarning"); - usbwarning.style.display = "block"; - } - } - - var dirCells = []; - var dataCells = []; - var index = 0; - - if (window.location.path != "/fs/") { - var clone = template.content.cloneNode(true); - var td = clone.querySelectorAll("td"); - td[0].textContent = "🗀"; - 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(); - - var sortdata = {}; - sortdata.value = ".."; - sortdata.index = index; - dirCells.push(sortdata); - index += 1; - - new_children.push(clone); - } - - 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"); - var icon = "⬇"; - var file_path = current_path + f.name; - let api_url = new URL("/fs" + file_path, url_base); - let edit_url = "/edit/#" + file_path; - if (f.directory) { - file_path = "#" + file_path + "/"; - api_url += "/"; - } else { - file_path = api_url; - } - - if (f.directory) { - icon = "🗀"; - } else if(f.name.endsWith(".txt") || - f.name.endsWith(".py") || - f.name.endsWith(".js") || - f.name.endsWith(".json")) { - icon = "🖹"; - } else if (f.name.endsWith(".html")) { - icon = "🌐"; - } - td[0].textContent = icon; - td[1].textContent = f.file_size; - var path = clone.querySelector("a"); - path.href = file_path; - path.textContent = f.name; - td[3].textContent = (new Date(f.modified_ns / 1000000)).toLocaleString(); - var delete_button = clone.querySelector("button.delete"); - delete_button.value = api_url; - delete_button.disabled = !editable; - delete_button.onclick = del; - - if (editable && !f.directory) { - edit_url = new URL(edit_url, url_base); - let edit_link = clone.querySelector(".edit_link"); - edit_link.href = edit_url - } - - var dataCell = td[sort_column]; - - var sortdata = {}; - sortdata.value = dataCell.textContent.toLowerCase().trim(); - sortdata.index = index; - if (!f.directory) { - dataCells.push(sortdata); - index += 1; - } else { - dirCells.push(sortdata); - index += 1; - } - - new_children.push(clone); - } - - dirCells.sort(compareValues); - dataCells.sort(compareValues); - - var tbody = document.querySelector("tbody"); - - // remove rows - while (tbody.firstChild) { - tbody.removeChild(tbody.lastChild); - } - - // add sorted rows - for (var i = 0; i < dirCells.length; i += 1) { - tbody.appendChild(new_children[dirCells[i].index]); - } - for (var i = 0; i < dataCells.length; i += 1) { - tbody.appendChild(new_children[dataCells[i].index]); - } -} - -async function find_devices() { - 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(); - } - 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) { - for (const file of files.files) { - let file_path = new URL("/fs" + current_path + file.name, url_base); - 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(); - files.value = ""; - upload_button.disabled = true; - } - } -} - -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(); - } - } -} - -find_devices(); - -let mkdir_button = document.getElementById("mkdir"); -mkdir_button.onclick = mkdir; - -let upload_button = document.getElementById("upload"); -upload_button.onclick = upload; - -upload_button.disabled = files.files.length == 0; - -files.onchange = () => { - upload_button.disabled = files.files.length == 0; -} - -mkdir_button.disabled = new_directory_name.value.length == 0; - -new_directory_name.oninput = () => { - mkdir_button.disabled = new_directory_name.value.length == 0; -} - -window.onhashchange = refresh_list; From 328fe4d2eac23cad79d328fee3dbe6bbda273c12 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 22:07:22 -0400 Subject: [PATCH 10/13] minor cleanup --- supervisor/shared/web_workflow/static/directory.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 9158bfbc87..a822d4a239 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -71,10 +71,8 @@ async function refresh_list() { // Remove the delete button td[4].replaceChildren(); - var dataCell = td[sort_column]; - var sortdata = {}; - sortdata.value = dataCell.textContent.toLowerCase().trim(); + sortdata.value = ".."; sortdata.index = index; dirCells.push(sortdata); index += 1; @@ -86,8 +84,6 @@ async function refresh_list() { // Clone the new row and insert it into the table var clone = template.content.cloneNode(true); var td = clone.querySelectorAll("td"); - var dataCell = td[sort_column]; - var icon = "⬇"; var file_path = current_path + f.name; let api_url = new URL("/fs" + file_path, url_base); From a87dcf201bb165ac97b908ed9c2b17ede26ab9a7 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Fri, 5 Aug 2022 22:35:57 -0400 Subject: [PATCH 11/13] Don't need a variable to identify 'Path' column --- supervisor/shared/web_workflow/static/directory.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index a822d4a239..a565100e12 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -1,7 +1,3 @@ -// var sort_column = undefined; -// (document.querySelectorAll("th")).forEach((element, indx) => { if (element.textContent == "Path") {sort_column = indx} } ); -var sort_column = 2; - let new_directory_name = document.getElementById("name"); let files = document.getElementById("files"); @@ -122,7 +118,7 @@ async function refresh_list() { edit_link.href = edit_url } - var dataCell = td[sort_column]; + var dataCell = td[2]; var sortdata = {}; sortdata.value = dataCell.textContent.toLowerCase().trim(); From c8a5149560cb7fddd0795d28dcaf23881b834d28 Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Tue, 9 Aug 2022 17:13:18 -0400 Subject: [PATCH 12/13] Sort json data rather than index of table data --- .../shared/web_workflow/static/directory.js | 49 +++---------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index a565100e12..1438e5c546 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -8,10 +8,11 @@ var editable = undefined; async function refresh_list() { function compareValues(a, b) { - if (a.value === b.value) { + if (a.directory == b.directory && a.name.toLowerCase() === b.name.toLowerCase()) { return 0; } else { - return a.value < b.value ? -1 : 1; + return a.directory.toString().substring(3,4)+a.name.toLowerCase() < + b.directory.toString().substring(3,4)+b.name.toLowerCase() ? -1 : 1; } } @@ -52,10 +53,6 @@ async function refresh_list() { } } - var dirCells = []; - var dataCells = []; - var index = 0; - if (window.location.path != "/fs/") { var clone = template.content.cloneNode(true); var td = clone.querySelectorAll("td"); @@ -66,16 +63,11 @@ async function refresh_list() { path.textContent = ".."; // Remove the delete button td[4].replaceChildren(); - - var sortdata = {}; - sortdata.value = ".."; - sortdata.index = index; - dirCells.push(sortdata); - index += 1; - new_children.push(clone); } + data.sort(compareValues); + for (const f of data) { // Clone the new row and insert it into the table var clone = template.content.cloneNode(true); @@ -118,39 +110,10 @@ async function refresh_list() { edit_link.href = edit_url } - var dataCell = td[2]; - - var sortdata = {}; - sortdata.value = dataCell.textContent.toLowerCase().trim(); - sortdata.index = index; - if (!f.directory) { - dataCells.push(sortdata); - index += 1; - } else { - dirCells.push(sortdata); - index += 1; - } - new_children.push(clone); } - - dirCells.sort(compareValues); - dataCells.sort(compareValues); - var tbody = document.querySelector("tbody"); - - // remove rows - while (tbody.firstChild) { - tbody.removeChild(tbody.lastChild); - } - - // add sorted rows - for (var i = 0; i < dirCells.length; i += 1) { - tbody.appendChild(new_children[dirCells[i].index]); - } - for (var i = 0; i < dataCells.length; i += 1) { - tbody.appendChild(new_children[dataCells[i].index]); - } + tbody.replaceChildren(...new_children); } async function find_devices() { From 0b286b7e7e3348d1d4941d79e28949a27ffc8a5c Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Wed, 10 Aug 2022 10:44:06 -0400 Subject: [PATCH 13/13] does pre-commit not like split lines? --- supervisor/shared/web_workflow/static/directory.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 1438e5c546..296b434ea2 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -11,8 +11,7 @@ async function refresh_list() { if (a.directory == b.directory && a.name.toLowerCase() === b.name.toLowerCase()) { return 0; } else { - return a.directory.toString().substring(3,4)+a.name.toLowerCase() < - b.directory.toString().substring(3,4)+b.name.toLowerCase() ? -1 : 1; + return a.directory.toString().substring(3,4)+a.name.toLowerCase() < b.directory.toString().substring(3,4)+b.name.toLowerCase() ? -1 : 1; } }