From f62af1058acf36efdaffcc8903eb12e544d8c29b Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Wed, 26 Jul 2023 17:47:00 +0200 Subject: [PATCH 01/11] Added docker compose for local developing --- docker-compose.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..8c04900 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,8 @@ +version: "3.9" +services: + web: + image: nginx:latest + volumes: + - .:/usr/share/nginx/html/ + ports: + - "8080:80" -- 2.17.1 From 7d477a22317c9b9c014758c2921379c6a4f7f63f Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Wed, 26 Jul 2023 18:08:15 +0200 Subject: [PATCH 02/11] Added sandbox index for the other page(s) --- index.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..1c6a62f --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + +
+

CDN sandbox

+

Select the section you want to enter

+
+
+
+ D4S Workspace (OIDC protected) +
+
+ + + -- 2.17.1 From 53bb1f97b646d1c6fd12c35b1b0189ad7219b9fa Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Wed, 26 Jul 2023 18:09:08 +0200 Subject: [PATCH 03/11] Added web page to test the D4S Storage WEB components and JS libraries --- storage/index.html | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 storage/index.html diff --git a/storage/index.html b/storage/index.html new file mode 100644 index 0000000..e1a0c60 --- /dev/null +++ b/storage/index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+
+
+ +
+
+ + + -- 2.17.1 From daa84c6d919bbe4ad107db7f4c16b5bfc36b806a Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Wed, 26 Jul 2023 18:14:11 +0200 Subject: [PATCH 04/11] Added `D4SWorkspace` class that interacts with WS WEB "rest" API and modificed the WEB components to use it. Some methods of the class, unused by web components at the moment, are still to be tested. --- storage/d4s-storage.js | 522 ++++++++++++++++++++++++++++++++++------- 1 file changed, 443 insertions(+), 79 deletions(-) diff --git a/storage/d4s-storage.js b/storage/d4s-storage.js index 95fff57..8694320 100644 --- a/storage/d4s-storage.js +++ b/storage/d4s-storage.js @@ -9,10 +9,13 @@ class D4SStorageHtmlElement extends HTMLElement { #baseurl = 'https://api.d4science.org/workspace' //#baseurl = 'https://workspace-repository.dev.d4science.org/storagehub/workspace' #boot = null + #d4sworkspace = null; + #showroot = false; // To show WS root folder by default set it to true constructor() { super() this.#boot = document.querySelector('d4s-boot-2') + this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot) } get boot() { @@ -43,7 +46,21 @@ class D4SStorageHtmlElement extends HTMLElement { set baseUrl(url) { this.#baseurl = url this.setAttribute("baseurl", url) + this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot) } + + get d4sWorkspace() { + return this.#d4sworkspace; + } + + get showRoot() { + return this.#showroot; + } + + set showRoot(show) { + this.#showroot = show; + } + } @@ -52,6 +69,7 @@ class D4SStorageHtmlElement extends HTMLElement { * { - if (reply.status !== 200) { - throw "Unable to load folder" - } - return reply.json() - - }).then(data => { - this.parseResponse(div, data) - this.#shadowRoot.appendChild(div) - + const initalFolderFunction = (cond) => {if (cond) return this.d4sWorkspace.getWorkspace(); else return this.d4sWorkspace.getVREFolder();} + initalFolderFunction(this.showRoot).then(data => { + this.parseResponse(div, data); + this.#shadowRoot.appendChild(div); }).catch(err => console.error(err)) } - parseResponse(parentElement, jresp) { - parentElement.classList.add(this.#loadedClass) - var ul = document.createElement('ul') - ul.classList.add('nested') - if (jresp.item) { - const itemname = jresp.item.displayName - const itemid = jresp.item.id - const path = this.buildListUrl(itemid) - ul.appendChild(this.createListItem(itemname, itemid)) - parentElement.appendChild(ul) - - } else if (jresp.itemlist) { - jresp - .itemlist - .filter(item => item['@class'].includes('FolderItem')) - .forEach(item => { - const itemname = item.name - const itemid = item.id - const path = this.buildListUrl(itemid) - ul.appendChild(this.createListItem(itemname, itemid)) - }) - parentElement.appendChild(ul) - + parseResponse(parentElement, data) { + parentElement.classList.add(this.#loadedClass); + var ul = document.createElement('ul'); + ul.classList.add('nested'); + if (Array.isArray(data)) { + data + .filter(item => item['@class'].includes('FolderItem')) + .forEach(item => { + const itemName = item.name; + const itemId = item.id; + const path = this.buildListUrl(itemId); + ul.appendChild(this.createListItem(itemName, itemId)); + }) + parentElement.appendChild(ul); } else { - console.error(jresp) + const itemName = data.displayName ? data.displayName : data.name; + const itemId = data.id; + const path = this.buildListUrl(itemId); + ul.appendChild(this.createListItem(itemName, itemId)); + parentElement.appendChild(ul); } } @@ -178,15 +185,8 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle return } - this.boot.secureFetch(this.buildListUrl(id)).then(reply => { - if (reply.status !== 200) { - throw "Unable to load folder" - } - return reply.json() - - }).then(data => { + this.d4sWorkspace.listFolder(id).then(data => { this.parseResponse(li, data) - }).catch(err => console.error(err)) } @@ -208,7 +208,7 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle } static get observedAttributes() { - return ["base-url", "filelist-id"] + return ["base-url", "filelist-id", "show-root"] } attributeChangedCallback(name, oldValue, newValue) { @@ -220,6 +220,8 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle case "base-url": this.baseUrl = newValue break + case "show-root": + this.showRoot = newValue; } } } @@ -290,24 +292,17 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE } list(folderId) { - this.boot.secureFetch(this.buildListUrl(folderId)).then(reply => { - if (reply.status !== 200) { - throw "Unable to build list url" - } - return reply.json() - - }).then(data => { - this.parseResponse(data) - - }).catch(err => console.error(err)) + this.d4sWorkspace.listFolder(folderId).then(data => { + this.parseResponse(data); + }).catch(err => console.error(err)); } - parseResponse(jresp) { - const root = this.createContainer() - if (jresp.itemlist) { - jresp.itemlist.forEach(item => root.appendChild(this.createFileRow(item))) + parseResponse(data) { + const root = this.createContainer(); + if (Array.isArray(data)) { + data.forEach(item => root.appendChild(this.createFileRow(item))); } else { - console.error(jresp) + console.error("Returned data is not an array: " + data); } } @@ -331,7 +326,7 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE } } else { console.info("Download of " + item.id) - this.download(this.buildDownloadUrl(item.id), item.name) + this.d4sWorkspace.download(item.id, item.name).catch(err => {alert(err)}); } }) @@ -351,29 +346,29 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE return '' + i + '' } - buildDownloadUrl(id) { - return this.baseUrl+ '/items/' + id + '/download' - } + // buildDownloadUrl(id) { + // return this.baseUrl+ '/items/' + id + '/download' + // } - download(url, name) { - this.boot.secureFetch(url).then(reply => { - if (reply.status !== 200) { - throw "Unable to download" - } - return reply.blob() + // download(url, name) { + // this.d4sWorkspace.download(url).then(reply => { + // if (reply.status !== 200) { + // throw "Unable to download" + // } + // return reply.blob() - }).then(blob => { - const objectURL = URL.createObjectURL(blob) - var tmplnk = document.createElement("a") - tmplnk.download = name - tmplnk.href = objectURL - //tmplnk.target="_blank" - document.body.appendChild(tmplnk) - tmplnk.click() - document.body.removeChild(tmplnk) + // }).then(blob => { + // const objectURL = URL.createObjectURL(blob) + // var tmplnk = document.createElement("a") + // tmplnk.download = name + // tmplnk.href = objectURL + // //tmplnk.target="_blank" + // document.body.appendChild(tmplnk) + // tmplnk.click() + // document.body.removeChild(tmplnk) - }).catch(err => console.error(err)) - } + // }).catch(err => console.error(err)) + // } static get observedAttributes() { return ["base-url"]; @@ -390,3 +385,372 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE } }) + +/** + * Class that helps the interaction with the D4Science's Workspace via API and user's token as bearer authentication + */ +class D4SWorkspace { + + #d4sboot = null; + #workspaceURL = null; + + /** + * Creates the new D4SWorkspace object, the d4sboot parameter can be null, in this case it is searched in the DOM document via querySelector() method + * @param {string} workspaceURL the WS API base URL + * @param {d4s-boot-2} d4sboot the d4s-boot-2 instance + */ + constructor(workspaceURL, d4sboot) { + this.setWorkspaceURL(workspaceURL) + if (d4sboot) { + this.setD4SBoot(d4sboot) + } else { + this.setD4SBoot(document.querySelector("d4s-boot-2")); + } + } + + /** + * Sets the Workspace API base URL + * @param {string} workspaceURL the WS API base URL + */ + setWorkspaceURL(workspaceURL) { + this.#workspaceURL = workspaceURL; + } + + /** + * Sets the d4s-boot-2 instance to be used to perform the OIDC/UMA autenticated secure fetches + * @param {string} d4sboot the d4s-boot-2 instance + */ + setD4SBoot(d4sboot) { + this.#d4sboot = d4sboot; + } + + /** + * Calls with a secure fetch the workspace with provided data. + * To upload data by using a FormData object you have to leave the mime attribute as null. + * @param {*} method the method to use for the call, default to GET + * @param {*} uri the uri to invoke, relative to workspaceURL provided in the object's constructor + * @param {*} body the payload to send + * @param {*} mime the mime type of the payload + * @param {*} extraHeaders extra HTTP headers to send + * @returns the reponse payload as a promise, JSON data promise if the returned data is "application/json", a String data promise if "text/*" or a blob data promise in other cases + * @throws the error as string, if occurred + */ + #callWorkspace(method, uri, body, mime, extraHeaders) { + let req = { }; + if (method) { + req.method = method; + } + if (body) { + req.body = body; + } + if (extraHeaders) { + req.headers = extraHeaders; + } + if (mime) { + if (req.headers) { + req.headers["Content-Type"] = mime; + } else { + req.headers = { "Content-Type" : mime }; + } + } + let url = this.#workspaceURL; + if (uri) { + url += uri.startsWith('/') ? uri : '/' + uri; + } + console.log("Invoking WS API with: " + url); + return this.#d4sboot.secureFetch(url, req) + .then(resp => { + if (resp.ok) { + const contentType = resp.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + return resp.json(); + } else if (contentType && contentType.indexOf("text/") !== -1) { + return resp.text(); + } else { + return resp.blob(); + } + } else { + throw "Cannot invoke workspace via secure fetch for URL: " + url; + } + }).catch(err => { + console.error(err); + throw err; + }); + } + + /** + * Gets the user's Workspace root folder JSON item + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the WS root folder JSON item object + * @throws the error as string, if occurred + */ + getWorkspace(extraHeaders) { + return this.#callWorkspace("GET", null, null, null, extraHeaders) + .then(json => { + return json.item; + }).catch(err => { + const msg = "Cannot get workspace root ID. Error message: " + err; + console.error(msg); + throw msg; + }); + } + + /** + * Gets the user's Workspace root folder id (as string) + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the id of the WS root folder + * @throws the error as string, if occurred + */ + getWorkspaceId(extraHeaders) { + return this.getWorkspace(extraHeaders) + .then(json => { + return json.item.id; + }); + } + + /** + * Gets the contents of a folder, all sub-folders and files. + * @param {string} folderId the WS folder id to list + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the contents of the WS folder as json objects + * @throws the error as string, if occurred + */ + listFolder(folderId, extraHeaders) { + const uri = "/items/" + folderId + "/children"; + return this.#callWorkspace("GET", uri, null, null, extraHeaders) + .then(json => { + return json.itemlist; + }).catch(err => { + const msg = "Cannot get folder's content list. Error message: " + err; + console.error(msg); + throw msg; + }); + } + + /** + * Gets the VRE folder JSON item that belongs to the context where the access token has been issued. + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the id of the WS VRE folder + * @throws the error as string, if occurred + */ + getVREFolder(extraHeaders) { + return this.#callWorkspace("GET", "/vrefolder", null, null, extraHeaders) + .then(json => { + return json.item; + }).catch(err => { + const msg = "Cannot get VRE folder ID. Error message: " + err; + console.error(msg); + throw msg; + }); + } + + /** + * Gets the VRE folder id (as string) that belongs to the context where the access token has been issued. + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the id of the WS VRE folder + * @throws the error as string, if occurred + */ + getVREFolderId(extraHeaders) { + return this.getVREFolder(extraHeaders) + .then(item => { + return item.id; + }); + } + + /** + * @deprecated please use getOrCreateFolder() function instead + * @param {string} parentFolderId the id of the parent folder where search fo the folder + * @param {string} name the name of the folder (characters not allowed: ":", [others still to be found]) + * @param {string} description the description of the folder if it should be creaded + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the id of the folder, if it exists or it has been just created + * @throws the error as string, if occurred + */ + checkOrCreateFolder(parentFolderId, name, description, extraHeaders) { + return this.getOrCreateFolder(parentFolderId, name, description, extraHeaders); + } + + /** + * Gets the id of the folder represented by the name parameter if exists, a new folder is created if it doesn't not exist + * @param {string} parentFolderId the id of the parent folder where search fo the folder + * @param {string} name the name of the folder (characters not allowed: ":", [others still to be found]) + * @param {string} description the description of the folder if it should be creaded + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the id of the folder, if it exists or it has been just created + * @throws the error as string, if occurred + */ + getOrCreateFolder(parentFolderId, name, description, extraHeaders) { + const baseURI = "/items/" + parentFolderId; + console.log("Checking existance of folder: " + name); + let uri = baseURI + "/items/" + name; + return this.#callWorkspace("GET", uri, null, null, extraHeaders) + .then(json => { + if (json.itemlist[0]) { + const id = json.itemlist[0].id; + console.log("'" + name + "' folder exists with and has id: " + id); + return id; + } else { + console.info("'" + name + "' folder doesn't exist, creating it: " + name); + uri = baseURI + "/create/FOLDER"; + const params = new URLSearchParams({ name : name, description : description, hidden : false }); + return this.#callWorkspace("POST", uri, params.toString(), "application/x-www-form-urlencoded", extraHeaders).then(id => { + console.log("New '" + name + "' folder successfully created with id: " + id); + return id + }); + } + }); + }; + + /** + * Deletes a Workspace item (folder or file) represented by its id. + * @param {string} itemId the id of the item do delete + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns true if the file has been correctly created, false otherwise + */ + deleteItem(itemId, extraHeaders) { + return this.#callWorkspace("DELETE", "/workspace/items/" + itemId, null, null, extraHeaders) + .then(results => { + return true; + }).catch(err => { + console.error("Cannot DELETE workspace item with ID: " + itemId + ". Error message: " + err); + return false; + }); + } + + /** + * Downloads a Workspace item (folder or file) represented by its id. + * @param {string} itemId the WS item to download + * @param {string} name the name of the file to use for the download, if null the id is used + * @param {boolean} skipLocalDownload if provided and true returns the data to function caller, otherwise it also downloads it locally. + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the file's content if item is a file or a ZIP compressed archive if the item represents a folder + * @throws the error as string, if occurred + */ + download(itemId, name, skipLocalDownload, extraHeaders) { + return this.#callWorkspace("GET", "/items/" + itemId + "/download", null, null, extraHeaders) + .then(data => { + if (!skipLocalDownload) { + console.log("Donwloads item also locally to browser"); + const objectURL = URL.createObjectURL(data); + var tmplnk = document.createElement("a"); + tmplnk.download = name ? name : itemId; + tmplnk.href = objectURL; + //tmplnk.target="_blank"; + document.body.appendChild(tmplnk); + tmplnk.click(); + document.body.removeChild(tmplnk); + } else { + console.log("Skipping local download"); + } + return data; + }).catch(err => { + const msg = "Cannot download item with ID: " + itemId + ". Error message: " + err; + console.error(msg); + throw msg; + }); + } + + /** + * Uploads a new file into the folder represented by its id + * @param {string} folderId the folder where to create the new file + * @param {string} name the name of the file to crete (characters not allowed: ":", [others still to be found]) + * @param {object} data the archive data + * @param {string} contentType the content type of the file + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns true if the file has been correctly created, false otherwise + */ + uploadFile(folderId, name, description, data, contentType, extraHeaders) { + const uri = "/items/" + folderId + "/create/FILE"; + const request = new FormData(); + request.append("name", name); + request.append("description", description); + request.append( + "file", + new Blob([data], { + type: contentType + }) + ); + return this.#callWorkspace("POST", uri, request, null, extraHeaders) + .then(item => { + const id = item.id; + console.info("File '" + name + "' successfully uploaded and its id is: " + id); + return true; + }).catch(err => { + console.error("Cannot upload file '" + name + "'. Error: " + err); + return false; + }); + }; + + /** + * Uploads a new archive and stores its content into the folder represented by its id + * @param {string} folderId the folder where to create the new file + * @param {string} parentFolderName the name of the folder to create (characters not allowed: ":", [others still to be found]) + * @param {object} data the file's contents + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns true if the file has been correctly created, false otherwise + */ + uploadArchive(folderId, parentFolderName, description, data, extraHeaders) { + const uri = "/items/" + folderId + "/create/ARCHIVE"; + const request = new FormData(); + request.append("parentFolderName", parentFolderName); + request.append( + "file", + new Blob([data], { + type: "application/octet-stream" + }) + ); + return this.#callWorkspace("POST", uri, request, null, extraHeaders) + .then(item => { + const id = item.id; + console.info("Archive file successfully uploaded and extracted into the '" + parentFolderName + "' folder, its id is: " + id); + return true; + }).catch(err => { + console.error("Cannot upload archive file. Error: " + err); + return false; + }); + }; + + /** + * Returns the public URL of a file + * @param {string} itemId the id of the item + * @param {int} version the optional file version + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the public URL of the item + * @throws the error as string, if occurred + */ + getPublicURL(itemId, version, extraHeaders) { + let uri = "/workspace/items/" + itemId + "/publiclink"; + if (version) { + uri += "version=" + version; + } + return this.#callWorkspace("GET", uri, null, null, extraHeaders) + .then(publicURL => { + return publicURL; + }).catch(err => { + const message = "Cannot retrieve the item's public URL. Error message: " + err; + console.error(message); + throw message; + }); + } + + /** + * Finds into a folder all the file files having the name than matches a specific pattern. + * Wildcard (*) can be used at the start or the end of the pattern (eg. starts with *{name}, ends with = {name}*) + * @param {string} folderId the WS folder id where to search + * @param {string} pattern the pattern to use for the search. Wildcard '*' can be used at the start or the end of the pattern (eg. starts with *{name}, ends with = {name}*) + * @param {map} extraHeaders extra HTTP headers to be used for the request + * @returns the contents of the WS folder as json objects + * @throws the error as string, if occurred + */ + findByName(folderId, pattern, extraHeaders) { + const uri = "/items/" + folderId + "/items/" + pattern; + return this.#callWorkspace("GET", uri, null, null, extraHeaders) + .then(json => { + return json.itemlist; + }).catch(err => { + const msg = "Cannot get folder's content list. Error message: " + err; + console.error(msg); + throw msg; + }); + } +} \ No newline at end of file -- 2.17.1 From ee3e100122f520ba0ff5a1160ac33c095fda7869 Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Tue, 8 Aug 2023 20:12:02 +0200 Subject: [PATCH 05/11] New icons --- storage/img/archive.svg | 3 +++ storage/img/arrow-clockwise.svg | 4 ++++ storage/img/box-arrow-down.svg | 4 ++++ storage/img/box-arrow-in-up.svg | 4 ++++ storage/img/download.svg | 4 ++++ storage/img/file-earmark-x.svg | 4 ++++ storage/img/file-earmark-zip.svg | 4 ++++ storage/img/folder-plus.svg | 4 ++++ storage/img/folder-x.svg | 4 ++++ 9 files changed, 35 insertions(+) create mode 100644 storage/img/archive.svg create mode 100644 storage/img/arrow-clockwise.svg create mode 100644 storage/img/box-arrow-down.svg create mode 100644 storage/img/box-arrow-in-up.svg create mode 100644 storage/img/download.svg create mode 100644 storage/img/file-earmark-x.svg create mode 100644 storage/img/file-earmark-zip.svg create mode 100644 storage/img/folder-plus.svg create mode 100644 storage/img/folder-x.svg diff --git a/storage/img/archive.svg b/storage/img/archive.svg new file mode 100644 index 0000000..b41be30 --- /dev/null +++ b/storage/img/archive.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/storage/img/arrow-clockwise.svg b/storage/img/arrow-clockwise.svg new file mode 100644 index 0000000..b072eb0 --- /dev/null +++ b/storage/img/arrow-clockwise.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/storage/img/box-arrow-down.svg b/storage/img/box-arrow-down.svg new file mode 100644 index 0000000..9a2ca12 --- /dev/null +++ b/storage/img/box-arrow-down.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/storage/img/box-arrow-in-up.svg b/storage/img/box-arrow-in-up.svg new file mode 100644 index 0000000..e6a4a7b --- /dev/null +++ b/storage/img/box-arrow-in-up.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/storage/img/download.svg b/storage/img/download.svg new file mode 100644 index 0000000..80a5817 --- /dev/null +++ b/storage/img/download.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/storage/img/file-earmark-x.svg b/storage/img/file-earmark-x.svg new file mode 100644 index 0000000..bedb970 --- /dev/null +++ b/storage/img/file-earmark-x.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/storage/img/file-earmark-zip.svg b/storage/img/file-earmark-zip.svg new file mode 100644 index 0000000..b82afcc --- /dev/null +++ b/storage/img/file-earmark-zip.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/storage/img/folder-plus.svg b/storage/img/folder-plus.svg new file mode 100644 index 0000000..c18e2a5 --- /dev/null +++ b/storage/img/folder-plus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/storage/img/folder-x.svg b/storage/img/folder-x.svg new file mode 100644 index 0000000..d571d08 --- /dev/null +++ b/storage/img/folder-x.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file -- 2.17.1 From 286312aee55116207c8e4ad6c054e63b99ed4f4d Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Tue, 8 Aug 2023 20:14:48 +0200 Subject: [PATCH 06/11] Removed useless params and tag --- storage/index.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/storage/index.html b/storage/index.html index e1a0c60..e048882 100644 --- a/storage/index.html +++ b/storage/index.html @@ -17,14 +17,12 @@
- -
-
- + +
- +
-- 2.17.1 From 33eedd6d48c9b5d867e3f54f70a986518f9289b1 Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Tue, 8 Aug 2023 20:16:26 +0200 Subject: [PATCH 07/11] Beta of the refactoring for modular use with toolbar and details with auto-linking and event driven communication. Drag and drop support is still to be implemented (if needed) --- storage/d4s-storage.js | 789 +++++++++++++++++++++++++++-------------- 1 file changed, 521 insertions(+), 268 deletions(-) diff --git a/storage/d4s-storage.js b/storage/d4s-storage.js index 8694320..618b3f8 100644 --- a/storage/d4s-storage.js +++ b/storage/d4s-storage.js @@ -5,168 +5,306 @@ var D4S_STORAGE_SCRIPT = document.currentScript */ class D4SStorageHtmlElement extends HTMLElement { - #d4smissmsg = 'Required d4s-boot-2 component not found' - #baseurl = 'https://api.d4science.org/workspace' - //#baseurl = 'https://workspace-repository.dev.d4science.org/storagehub/workspace' - #boot = null - #d4sworkspace = null; - #showroot = false; // To show WS root folder by default set it to true + #srcbaseurl = null + + constructor() { + super() + if (D4S_STORAGE_SCRIPT && D4S_STORAGE_SCRIPT.src) { + this.#srcbaseurl = D4S_STORAGE_SCRIPT.src.substring(0, D4S_STORAGE_SCRIPT.src.lastIndexOf('/')); + } + this.attachShadow({mode: 'open'}) + } + + static appendBootstrapStylesheets(root) { + let linkElem = document.createElement('link'); + linkElem.setAttribute('rel', 'stylesheet'); + linkElem.setAttribute('href', 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css'); + root.appendChild(linkElem); + } + + get srcBaseURL() { + return this.#srcbaseurl; + } + +} + +class D4SStorageToolbar extends HTMLElement { + + static toolbar_event_name = "d4s-toolbar"; + + static refresh_folder = "refresh-folder"; + static create_folder = "create-folder"; + static delete_folder = "delete-folder"; + static download = "download"; + static upload_file = "upload-file"; + static upload_archive = "upload-archive"; + + #last_action = null; constructor() { super() - this.#boot = document.querySelector('d4s-boot-2') - this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot) + this.attachShadow({mode: 'open'}) + } + + connectedCallback() { + D4SStorageHtmlElement.appendBootstrapStylesheets(this.shadowRoot); + + var div = document.createElement('div'); + div.innerHTML = /*html*/` +
+ Refresh folder contents + Create new folder + Delete folder + Download folder as ZIP + Upload file + Upload archive +
`; + div.querySelector("#refresh").addEventListener('click', (ev) => { + ev.stopPropagation(); + this.actionPerformed(D4SStorageToolbar.refresh_folder); + }); + div.querySelector("#create").addEventListener('click', (ev) => { + ev.stopPropagation(); + this.actionPerformed(D4SStorageToolbar.create_folder); + }); + div.querySelector("#delete").addEventListener('click', (ev) => { + ev.stopPropagation(); + this.actionPerformed(D4SStorageToolbar.delete_folder); + }); + div.querySelector("#download").addEventListener('click', (ev) => { + ev.stopPropagation(); + this.actionPerformed(D4SStorageToolbar.download); + }); + div.querySelector("#upload-file").addEventListener('click', (ev) => { + ev.stopPropagation(); + this.actionPerformed(D4SStorageToolbar.upload_file); + }); + div.querySelector("#upload-archive").addEventListener('click', (ev) => { + ev.stopPropagation(); + this.actionPerformed(D4SStorageToolbar.upload_archive); + }); + this.shadowRoot.appendChild(div); + } + + actionPerformed(action) { + this.#last_action = action; + this.dispatchEvent(new CustomEvent(D4SStorageToolbar.toolbar_event_name, {detail: action})); + } + + get lastAction() { + return this.#last_action; + } + +} + +class D4SStorageTree extends D4SStorageHtmlElement { + + static tree_event_name = "d4s-toolbar"; + static folder_data_event_name = "d4s-folder-data"; + + static dataid_attr = 'data-id'; + static dataname_attr = 'data-name'; + static parentid_attr = 'parent-id'; + + #loadedClass = 'loaded' + #selectedClass = 'selected' + #selectedbgcolor = 'lightgray' + + #boot = null + #baseurl = 'https://api.d4science.org/workspace' + #d4sworkspace = null; + #foldersMap = {}; + #currentid = null; + + constructor() { + super() + this.#boot = document.querySelector('d4s-boot-2'); + this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot); } get boot() { - if (this.#boot == null) { - this.#boot = document.querySelector('d4s-boot-2') - if (!this.#boot) { - throw this.#d4smissmsg - } - } - return this.#boot + return this.#boot; } - - appendStylesheets(root) { - const linkElem1 = document.createElement('link') - linkElem1.setAttribute('rel', 'stylesheet') - linkElem1.setAttribute('href', 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css') - root.appendChild(linkElem1) - } - - buildListUrl(id) { - return this.#baseurl + '/items/' + id + '/children' - } - get baseUrl() { - return this.#baseurl + return this.#baseurl; } set baseUrl(url) { - this.#baseurl = url - this.setAttribute("baseurl", url) - this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot) + this.#baseurl = url; + // this.setAttribute("baseurl", url) + this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot); } get d4sWorkspace() { return this.#d4sworkspace; } - get showRoot() { - return this.#showroot; + set currentId(id) { + this.#currentid = id; } - set showRoot(show) { - this.#showroot = show; + get currentId() { + return this.#currentid; } -} - - -/** - * Builds storagehub VREFolder tree - * and if "id": "e0cd15d2-0071-43ca-bc42-aef8d80660fe", - the tree dir of vrefolder: https://storagehub.pre.d4science.net/storagehub/workspace/items/e0cd15d2-0071-43ca-bc42-aef8d80660fe/children - download of a file (domenica.jpg): https://storagehub.pre.d4science.net/storagehub/workspace/items/a5086811-59d1-4230-99fa-98431e820cf1/download - */ connectedCallback() { - this.appendStylesheets(this.#shadowRoot) + D4SStorageHtmlElement.appendBootstrapStylesheets(this.shadowRoot) var div = document.createElement('div') - const style = ` + div.innerHTML = /*css*/` - ` - div.innerHTML = style + `; - const initalFolderFunction = (cond) => {if (cond) return this.d4sWorkspace.getWorkspace(); else return this.d4sWorkspace.getVREFolder();} - initalFolderFunction(this.showRoot).then(data => { - this.parseResponse(div, data); - this.#shadowRoot.appendChild(div); + this.shadowRoot.appendChild(div); + + this.d4sWorkspace.getWorkspace().then(data => { + this.parseItemsData(div, data); + }).catch(err => console.error(err)); + + this.d4sWorkspace.getVREFolder().then(data => { + this.parseItemsData(div, data); }).catch(err => console.error(err)) - } - parseResponse(parentElement, data) { - parentElement.classList.add(this.#loadedClass); - var ul = document.createElement('ul'); - ul.classList.add('nested'); - if (Array.isArray(data)) { - data - .filter(item => item['@class'].includes('FolderItem')) - .forEach(item => { - const itemName = item.name; - const itemId = item.id; - const path = this.buildListUrl(itemId); - ul.appendChild(this.createListItem(itemName, itemId)); - }) - parentElement.appendChild(ul); + this.addEventListener(D4SStorageTree.tree_event_name, this.localEventHandler); + this.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler); + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", this.registerOtherListeners.bind(this)); } else { - const itemName = data.displayName ? data.displayName : data.name; - const itemId = data.id; - const path = this.buildListUrl(itemId); - ul.appendChild(this.createListItem(itemName, itemId)); - parentElement.appendChild(ul); + this.registerListeners(); } } - createListItem(label, id) { - var li = document.createElement('li') - li.setAttribute('data-id', id) - var child = document.createElement('span') - child.innerHTML = label - li.appendChild(child) + disconnectedCallback() { + this.removeEventListener(D4SStorageTree.tree_event_name); + this.removeEventListener(D4SStorageTree.folder_data_event_name); + const toolbar = document.querySelector("d4s-storage-tool"); + if (toolbar) toolbar.removeEventListener(D4SStorageToolbar.toolbar_event_name); + const folder = document.querySelector("d4s-storage-folder"); + if (folder) folder.removeEventListener(D4SStorageFolder.selected_event_name); + } - li.addEventListener('click', (ev) => { - ev.stopPropagation() - this.select(ev.currentTarget.getAttribute('data-id')) - }) - return li + registerOtherListeners(event) { + const toolbar = document.querySelector("d4s-storage-tool"); + if (toolbar) toolbar.addEventListener(D4SStorageToolbar.toolbar_event_name, this.toolbarEventHandler.bind(this)); + const folder = document.querySelector("d4s-storage-folder"); + if (folder) folder.addEventListener(D4SStorageFolder.selected_event_name, this.folderEventHandler.bind(this)); + } + + localEventHandler(event) { + this.select(event.detail); + } + + folderDataEventHandler(event) { + if (event.detail === this.currentId) { + this.fillWithContent(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${this.currentId}']`)) + } + } + + toolbarEventHandler(event) { + switch (event.detail) { + case D4SStorageToolbar.refresh_folder: + if (this.currentId) { + this.refreshFolder(this.currentId); + } + break; + case D4SStorageToolbar.create_folder: + if (this.currentId) { + this.createNewFolderIn(this.currentId); + } + break; + case D4SStorageToolbar.delete_folder: + if (this.currentId) { + this.delete(this.currentId); + } + break; + case D4SStorageToolbar.download: + if (this.currentId) { + const name = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${this.currentId}']`).getAttribute(D4SStorageTree.dataname_attr); + this.d4sWorkspace.download(this.currentId, name); + } + break; + case D4SStorageToolbar.upload_file: + var file = document.createElement('input'); + file.type = 'file'; + file.addEventListener('change', (ev) => { + const selectedFile = file.files[0]; + if (selectedFile) { + this.uploadFile(this.currentId, selectedFile); + } + }); + file.click(); + break; + case D4SStorageToolbar.upload_archive: + var file = document.createElement('input'); + file.type = 'file'; + file.addEventListener('change', (ev) => { + const selectedFile = file.files[0]; + if (selectedFile) { + this.uploadArchive(this.currentId, selectedFile); + } + }); + file.click(); + break; + default: + console.error('Unexpected event action: ' + event.action); + break; + } + } + + folderEventHandler(event) { + switch (event.detail.itemtype) { + case D4SStorageFolder.file_selected_detail_itemtype: + console.info("Download of file: " + event.detail.name + " [" + event.detail.id + "]"); + this.d4sWorkspace.download(event.detail.id, event.detail.name).catch(err => {alert(err)}); + break; + case D4SStorageFolder.folder_selected_detail_itemtype: + this.select(event.detail.id); + break; + default: + console.warn("Unexpected folder event: " + event.detail.itemtype); + break; + } } select(id) { - var li = this.shadowRoot.querySelector(`*[data-id='${id}']`) - this.displayPath(li) + this.currentId = id; + var li = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); + this.displayPath(li); - // if it was already selected then do nothing if (li.classList.contains(this.#selectedClass)) { - this.toggleULDisplay(li) - return + this.toggleULDisplay(li); } // switch selected and lists folder contents if (!li.classList.contains(this.#selectedClass)) { @@ -175,19 +313,73 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle selected.classList.remove(this.#selectedClass) } li.classList.add(this.#selectedClass) - if (this.#storageFilelistId) { - var folder = document.getElementById(this.#storageFilelistId) - folder.join(this) - folder.list(id) - } } if (li.classList.contains(this.#loadedClass)) { return } - this.d4sWorkspace.listFolder(id).then(data => { - this.parseResponse(li, data) - }).catch(err => console.error(err)) + this.fillWithContent(li); + } + + refreshContents(element) { + [...element.querySelectorAll(`ul`)].forEach(ul => { + ul.remove(); + }); + this.fillWithContent(element); + } + + fillWithContent(element) { + const id = element.getAttribute(D4SStorageTree.dataid_attr); + if (this.#foldersMap[id]) { + this.parseItemsData(element, this.#foldersMap[id]); + } else { + this.d4sWorkspace.listFolder(id).then(data => { + this.setFolderItems(id, data); + }).catch(err => console.error(err)); + } + } + + parseItemsData(parentElement, data) { + parentElement.classList.add(this.#loadedClass); + const parentId = parentElement.getAttribute(D4SStorageTree.dataid_attr); + var ul = document.createElement('ul'); + ul.classList.add('nested'); + if (!parentId) { + ul.classList.add('root'); + } + if (Array.isArray(data)) { + data + .filter(item => item['@class'].includes('FolderItem')) + .forEach(item => { + ul.appendChild(this.createListItem(item.name, item.id, parentId)); + }) + } else { + ul.appendChild(this.createListItem(data.displayName ? data.displayName : data.name, data.id, parentId)); + } + parentElement.appendChild(ul); + } + + createListItem(label, id, parentId) { + const li = document.createElement('li'); + li.setAttribute(D4SStorageTree.dataname_attr, label); + li.setAttribute(D4SStorageTree.dataid_attr, id); + if (parentId) { + li.setAttribute(D4SStorageTree.parentid_attr, parentId); + } + var child = document.createElement('span'); + child.innerHTML = /*html*/`` + label; + li.appendChild(child) + + li.addEventListener('click', (ev) => { + ev.stopPropagation(); + this.dispatchEvent( + new CustomEvent( + D4SStorageTree.tree_event_name, + {detail: ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr)} + ) + ); + }) + return li; } displayPath(li) { @@ -208,60 +400,98 @@ window.customElements.define('d4s-storage-tree', class extends D4SStorageHtmlEle } static get observedAttributes() { - return ["base-url", "filelist-id", "show-root"] + return ["base-url"]; } attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue) { switch (name) { - case "filelist-id": - this.#storageFilelistId = newValue - break case "base-url": this.baseUrl = newValue break - case "show-root": - this.showRoot = newValue; + default: + console.warn("Unexpected attribute changed: " + name); } } } - get filelistId() { - return this.#storageFilelistId + refreshFolder(id) { + const folder = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); + console.log("Refreshing folder: " + folder.getAttribute(D4SStorageTree.dataname_attr)); + this.refreshContents(folder); + this.select(id); } - set filelistId(id) { - this.#storageFilelistId = id - this.setAttribute("filelist-id", id) - } -}) - -/** - * Lists elements in selected folder - * - * { + window.setTimeout(()=>{ + this.refreshContents(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`)); + this.select(id); + }, 2000); + }).catch(err => { + alert(err); + }) } } + delete(id) { + const currentLI = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); + const name = currentLI.getAttribute(D4SStorageTree.dataname_attr); + if (currentLI.getAttribute(D4SStorageTree.parentid_attr)) { + if (confirm(`Do you really want to delete folder '${name}'?`)) { + if(this.d4sWorkspace.deleteItem(id)) { + window.setTimeout(()=>{ + const parentFolderId = currentLI.getAttribute(D4SStorageTree.parentid_attr); + this.refreshContents(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${parentFolderId}']`)); + this.select(parentFolderId); + }, 2000); + } else { + alert(err); + } + } + } else { + alert("You cannot delete ROOT folder: "+ name); + } + } + + uploadFile(targetFolderId, file) { + if (this.d4sWorkspace.uploadFile(targetFolderId, file.name, "", file, file.type)) { + window.setTimeout(()=>{ + this.select(targetFolderId); + }, 2000); + } + } + + uploadArchive(targetFolderId, file) { + if (this.d4sWorkspace.uploadArchive(targetFolderId, file.name.substring(0, file.name.lastIndexOf(".")), file)) { + window.setTimeout(()=>{ + this.refreshContents(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${targetFolderId}']`)); + this.select(targetFolderId); + }, 3000); + } + } + +} + +class D4SStorageFolder extends D4SStorageHtmlElement { + + static selected_event_name = "d4s-folder-selected"; + + static folder_selected_detail_itemtype = "folder_selected"; + static file_selected_detail_itemtype = "file_selected"; + + #selectedbgcolor = 'lightgray' + + constructor() { + super() + } + connectedCallback() { - const shadowRoot = this.attachShadow({mode: 'open'}) - this.appendStylesheets(shadowRoot) + D4SStorageHtmlElement.appendBootstrapStylesheets(this.shadowRoot); const style = document.createElement('style') - style.innerHTML = ` + style.innerHTML = /*css*/` span { cursor: pointer; user-select: none; @@ -270,8 +500,48 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE -ms-user-select: none; } ` - shadowRoot.appendChild(style) - this.createContainer() + this.shadowRoot.appendChild(style); + this.createContainer(); + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", this.registerOtherListeners.bind(this)); + } else { + this.registerListeners(); + } + } + + disconnectedCallback() { + const tree = document.querySelector("d4s-storage-tree"); + if (tree) { + tree.removeEventListener(D4SStorageTree.tree_event_name); + tree.removeEventListener(D4SStorageTree.folder_data_event_name); + } + } + + registerOtherListeners(event) { + const tree = document.querySelector("d4s-storage-tree"); + if (tree) { + tree.addEventListener(D4SStorageTree.tree_event_name, this.treeEventHandler.bind(this)); + tree.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler.bind(this)); + } + } + + treeEventHandler(event) { + const folderItems = event.target.getFolderItems(event.detail); + if (folderItems) { + // It's first time folder is selected, data is stil not loaded, + // they will be loaded by the folderDataEventHandler() function when available + this.parseItemsData(folderItems); + } + } + + folderDataEventHandler(event) { + if (event.detail === event.target.currentId) { + const folderItems = event.target.getFolderItems(event.detail); + if (folderItems) { + this.parseItemsData(folderItems); + } + } } createContainer() { @@ -285,19 +555,7 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE return div } - join(tree) { - if (this.#d4sstorageTree == null) { - this.#d4sstorageTree = tree - } - } - - list(folderId) { - this.d4sWorkspace.listFolder(folderId).then(data => { - this.parseResponse(data); - }).catch(err => console.error(err)); - } - - parseResponse(data) { + parseItemsData(data) { const root = this.createContainer(); if (Array.isArray(data)) { data.forEach(item => root.appendChild(this.createFileRow(item))); @@ -311,8 +569,8 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE div.classList.add('row') var filename = document.createElement('div') filename.classList.add('col') - var name = `${item.name}` - filename.innerHTML = this.iconTag(item) + name + var spanAndName = /*html*/`${item.name}` + filename.innerHTML = this.iconTag(item) + spanAndName const isFolder = item['@class'].includes('FolderItem') filename.addEventListener('click', (ev) => { ev.stopPropagation() @@ -321,12 +579,19 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE if (span) { span.style = 'background-color: ' + this.#selectedbgcolor } - if (this.#d4sstorageTree != null) { - this.#d4sstorageTree.select(item.id) - } + this.dispatchEvent( + new CustomEvent( + D4SStorageFolder.selected_event_name, + {detail: {itemtype: D4SStorageFolder.folder_selected_detail_itemtype, id: item.id, name: item.name}} + ) + ); } else { - console.info("Download of " + item.id) - this.d4sWorkspace.download(item.id, item.name).catch(err => {alert(err)}); + this.dispatchEvent( + new CustomEvent( + D4SStorageFolder.selected_event_name, + {detail: {itemtype: D4SStorageFolder.file_selected_detail_itemtype, id: item.id, name: item.name}} + ) + ); } }) @@ -335,56 +600,18 @@ window.customElements.define('d4s-storage-folder', class extends D4SStorageHtmlE } iconTag(item) { - var i = `` + var i = /*html*/`` if (item['@class'].includes('FolderItem')) { - i = `` + i = /*html*/`` } else if (item['@class'].includes('ImageFile')) { - i = `` + i = /*html*/`` } else if (item['@class'].includes('PDFFileItem')) { - i = `` + i = /*html*/`` } return '' + i + '' } - // buildDownloadUrl(id) { - // return this.baseUrl+ '/items/' + id + '/download' - // } - - // download(url, name) { - // this.d4sWorkspace.download(url).then(reply => { - // if (reply.status !== 200) { - // throw "Unable to download" - // } - // return reply.blob() - - // }).then(blob => { - // const objectURL = URL.createObjectURL(blob) - // var tmplnk = document.createElement("a") - // tmplnk.download = name - // tmplnk.href = objectURL - // //tmplnk.target="_blank" - // document.body.appendChild(tmplnk) - // tmplnk.click() - // document.body.removeChild(tmplnk) - - // }).catch(err => console.error(err)) - // } - - static get observedAttributes() { - return ["base-url"]; - } - - attributeChangedCallback(name, oldValue, newValue) { - if (oldValue !== newValue) { - switch (name) { - case "base-url": - this.baseUrl = newValue - break - } - } - } - -}) +} /** * Class that helps the interaction with the D4Science's Workspace via API and user's token as bearer authentication @@ -432,7 +659,7 @@ class D4SWorkspace { * @param {*} body the payload to send * @param {*} mime the mime type of the payload * @param {*} extraHeaders extra HTTP headers to send - * @returns the reponse payload as a promise, JSON data promise if the returned data is "application/json", a String data promise if "text/*" or a blob data promise in other cases + * @returns the reponse payload as a Blob() promise * @throws the error as string, if occurred */ #callWorkspace(method, uri, body, mime, extraHeaders) { @@ -461,14 +688,7 @@ class D4SWorkspace { return this.#d4sboot.secureFetch(url, req) .then(resp => { if (resp.ok) { - const contentType = resp.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1) { - return resp.json(); - } else if (contentType && contentType.indexOf("text/") !== -1) { - return resp.text(); - } else { - return resp.blob(); - } + return resp.blob(); } else { throw "Cannot invoke workspace via secure fetch for URL: " + url; } @@ -486,8 +706,10 @@ class D4SWorkspace { */ getWorkspace(extraHeaders) { return this.#callWorkspace("GET", null, null, null, extraHeaders) - .then(json => { - return json.item; + .then(blob => { + return blob.text().then(text => { + return JSON.parse(text).item; + }) }).catch(err => { const msg = "Cannot get workspace root ID. Error message: " + err; console.error(msg); @@ -518,8 +740,10 @@ class D4SWorkspace { listFolder(folderId, extraHeaders) { const uri = "/items/" + folderId + "/children"; return this.#callWorkspace("GET", uri, null, null, extraHeaders) - .then(json => { - return json.itemlist; + .then(blob => { + return blob.text().then(text => { + return JSON.parse(text).itemlist; + }); }).catch(err => { const msg = "Cannot get folder's content list. Error message: " + err; console.error(msg); @@ -535,8 +759,10 @@ class D4SWorkspace { */ getVREFolder(extraHeaders) { return this.#callWorkspace("GET", "/vrefolder", null, null, extraHeaders) - .then(json => { - return json.item; + .then(blob => { + return blob.text().then(text => { + return JSON.parse(text).item; + }); }).catch(err => { const msg = "Cannot get VRE folder ID. Error message: " + err; console.error(msg); @@ -584,20 +810,23 @@ class D4SWorkspace { console.log("Checking existance of folder: " + name); let uri = baseURI + "/items/" + name; return this.#callWorkspace("GET", uri, null, null, extraHeaders) - .then(json => { - if (json.itemlist[0]) { - const id = json.itemlist[0].id; - console.log("'" + name + "' folder exists with and has id: " + id); - return id; - } else { - console.info("'" + name + "' folder doesn't exist, creating it: " + name); - uri = baseURI + "/create/FOLDER"; - const params = new URLSearchParams({ name : name, description : description, hidden : false }); - return this.#callWorkspace("POST", uri, params.toString(), "application/x-www-form-urlencoded", extraHeaders).then(id => { - console.log("New '" + name + "' folder successfully created with id: " + id); - return id - }); - } + .then(blob => { + return blob.text().then(text => { + const json = JSON.parse(text); + if (json.itemlist[0]) { + const id = json.itemlist[0].id; + console.log("'" + name + "' folder exists with and has id: " + id); + return id; + } else { + console.info("'" + name + "' folder doesn't exist, creating it: " + name); + uri = baseURI + "/create/FOLDER"; + const params = new URLSearchParams({ name : name, description : description, hidden : false }); + return this.#callWorkspace("POST", uri, params.toString(), "application/x-www-form-urlencoded", extraHeaders).then(id => { + console.log("New '" + name + "' folder successfully created with id: " + id); + return id + }); + } + }); }); }; @@ -608,7 +837,7 @@ class D4SWorkspace { * @returns true if the file has been correctly created, false otherwise */ deleteItem(itemId, extraHeaders) { - return this.#callWorkspace("DELETE", "/workspace/items/" + itemId, null, null, extraHeaders) + return this.#callWorkspace("DELETE", "/items/" + itemId, null, null, extraHeaders) .then(results => { return true; }).catch(err => { @@ -623,7 +852,7 @@ class D4SWorkspace { * @param {string} name the name of the file to use for the download, if null the id is used * @param {boolean} skipLocalDownload if provided and true returns the data to function caller, otherwise it also downloads it locally. * @param {map} extraHeaders extra HTTP headers to be used for the request - * @returns the file's content if item is a file or a ZIP compressed archive if the item represents a folder + * @returns the file's content, if item is a file, or a ZIP compressed archive if the item represents a folder, as a Blob object * @throws the error as string, if occurred */ download(itemId, name, skipLocalDownload, extraHeaders) { @@ -671,15 +900,16 @@ class D4SWorkspace { }) ); return this.#callWorkspace("POST", uri, request, null, extraHeaders) - .then(item => { - const id = item.id; - console.info("File '" + name + "' successfully uploaded and its id is: " + id); - return true; + .then(blob => { + return blob.text().then(id => { + console.info("File '" + name + "' successfully uploaded and its id is: " + id); + return true; + }) }).catch(err => { console.error("Cannot upload file '" + name + "'. Error: " + err); return false; }); - }; + } /** * Uploads a new archive and stores its content into the folder represented by its id @@ -689,7 +919,7 @@ class D4SWorkspace { * @param {map} extraHeaders extra HTTP headers to be used for the request * @returns true if the file has been correctly created, false otherwise */ - uploadArchive(folderId, parentFolderName, description, data, extraHeaders) { + uploadArchive(folderId, parentFolderName, data, extraHeaders) { const uri = "/items/" + folderId + "/create/ARCHIVE"; const request = new FormData(); request.append("parentFolderName", parentFolderName); @@ -700,15 +930,16 @@ class D4SWorkspace { }) ); return this.#callWorkspace("POST", uri, request, null, extraHeaders) - .then(item => { - const id = item.id; - console.info("Archive file successfully uploaded and extracted into the '" + parentFolderName + "' folder, its id is: " + id); - return true; + .then(blob => { + return blob.text().then(id => { + console.info("Archive file successfully uploaded and extracted into the '" + parentFolderName + "' folder, its id is: " + id); + return true; + }) }).catch(err => { console.error("Cannot upload archive file. Error: " + err); return false; }); - }; + } /** * Returns the public URL of a file @@ -724,8 +955,10 @@ class D4SWorkspace { uri += "version=" + version; } return this.#callWorkspace("GET", uri, null, null, extraHeaders) - .then(publicURL => { - return publicURL; + .then(blob => { + return blob.text().then(publicURL => { + return publicURL; + }) }).catch(err => { const message = "Cannot retrieve the item's public URL. Error message: " + err; console.error(message); @@ -745,12 +978,32 @@ class D4SWorkspace { findByName(folderId, pattern, extraHeaders) { const uri = "/items/" + folderId + "/items/" + pattern; return this.#callWorkspace("GET", uri, null, null, extraHeaders) - .then(json => { - return json.itemlist; + .then(blob => { + return blob.text().then(text => { + return JSON.parse(text).itemlist; + }); }).catch(err => { const msg = "Cannot get folder's content list. Error message: " + err; console.error(msg); throw msg; }); } -} \ No newline at end of file +} + +/** + * Builds storagehub toolbar and raises an event for each selected tool item + * + */ +window.customElements.define('d4s-storage-tool', D4SStorageToolbar); + +/** + * Builds storagehub VRE folder or Workspace root tree, linked to toolbar and foldr list by events + * + */ +window.customElements.define('d4s-storage-folder', D4SStorageFolder); -- 2.17.1 From fbd44d3caf05c53fabacf0987c0c66836c2efd8d Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Wed, 9 Aug 2023 12:39:18 +0200 Subject: [PATCH 08/11] Beta2 with lot of debugging fixes, no new featurs --- storage/d4s-storage.js | 108 ++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/storage/d4s-storage.js b/storage/d4s-storage.js index 618b3f8..a384523 100644 --- a/storage/d4s-storage.js +++ b/storage/d4s-storage.js @@ -15,11 +15,11 @@ class D4SStorageHtmlElement extends HTMLElement { this.attachShadow({mode: 'open'}) } - static appendBootstrapStylesheets(root) { + connectedCallback() { let linkElem = document.createElement('link'); linkElem.setAttribute('rel', 'stylesheet'); linkElem.setAttribute('href', 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css'); - root.appendChild(linkElem); + this.shadowRoot.appendChild(linkElem); } get srcBaseURL() { @@ -28,7 +28,7 @@ class D4SStorageHtmlElement extends HTMLElement { } -class D4SStorageToolbar extends HTMLElement { +class D4SStorageToolbar extends D4SStorageHtmlElement { static toolbar_event_name = "d4s-toolbar"; @@ -43,21 +43,20 @@ class D4SStorageToolbar extends HTMLElement { constructor() { super() - this.attachShadow({mode: 'open'}) } connectedCallback() { - D4SStorageHtmlElement.appendBootstrapStylesheets(this.shadowRoot); + super.connectedCallback(); var div = document.createElement('div'); div.innerHTML = /*html*/`
- Refresh folder contents - Create new folder - Delete folder - Download folder as ZIP - Upload file - Upload archive + Refresh folder contents + Create new folder + Delete folder + Download folder as ZIP + Upload file + Upload archive
`; div.querySelector("#refresh").addEventListener('click', (ev) => { ev.stopPropagation(); @@ -88,7 +87,7 @@ class D4SStorageToolbar extends HTMLElement { actionPerformed(action) { this.#last_action = action; - this.dispatchEvent(new CustomEvent(D4SStorageToolbar.toolbar_event_name, {detail: action})); + this.dispatchEvent(new CustomEvent(D4SStorageToolbar.toolbar_event_name, {detail: {action: action}})); } get lastAction() { @@ -147,14 +146,16 @@ class D4SStorageTree extends D4SStorageHtmlElement { return this.#currentid; } - setFolderItems(id, items) { + setFolderItems(id, items, fireEvent = true) { this.#foldersMap[id] = items; - this.dispatchEvent( - new CustomEvent( - D4SStorageTree.folder_data_event_name, - {detail: id} - ) - ); + if (fireEvent) { + this.dispatchEvent( + new CustomEvent( + D4SStorageTree.folder_data_event_name, + {detail: {id: id}} + ) + ); + } } getFolderItems(id) { @@ -166,7 +167,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { } connectedCallback() { - D4SStorageHtmlElement.appendBootstrapStylesheets(this.shadowRoot) + super.connectedCallback(); var div = document.createElement('div') div.innerHTML = /*css*/` @@ -197,7 +198,6 @@ class D4SStorageTree extends D4SStorageHtmlElement { this.parseItemsData(div, data); }).catch(err => console.error(err)) - this.addEventListener(D4SStorageTree.tree_event_name, this.localEventHandler); this.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", this.registerOtherListeners.bind(this)); @@ -207,7 +207,6 @@ class D4SStorageTree extends D4SStorageHtmlElement { } disconnectedCallback() { - this.removeEventListener(D4SStorageTree.tree_event_name); this.removeEventListener(D4SStorageTree.folder_data_event_name); const toolbar = document.querySelector("d4s-storage-tool"); if (toolbar) toolbar.removeEventListener(D4SStorageToolbar.toolbar_event_name); @@ -222,18 +221,14 @@ class D4SStorageTree extends D4SStorageHtmlElement { if (folder) folder.addEventListener(D4SStorageFolder.selected_event_name, this.folderEventHandler.bind(this)); } - localEventHandler(event) { - this.select(event.detail); - } - folderDataEventHandler(event) { - if (event.detail === this.currentId) { + if (event.detail.id === this.currentId) { this.fillWithContent(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${this.currentId}']`)) } } toolbarEventHandler(event) { - switch (event.detail) { + switch (event.detail.action) { case D4SStorageToolbar.refresh_folder: if (this.currentId) { this.refreshFolder(this.currentId); @@ -278,7 +273,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { file.click(); break; default: - console.error('Unexpected event action: ' + event.action); + console.error('Unexpected event action: ' + event.detail.action); break; } } @@ -314,11 +309,16 @@ class D4SStorageTree extends D4SStorageHtmlElement { } li.classList.add(this.#selectedClass) } - if (li.classList.contains(this.#loadedClass)) { - return + if (!li.classList.contains(this.#loadedClass)) { + this.fillWithContent(li); } - this.fillWithContent(li); + this.dispatchEvent( + new CustomEvent( + D4SStorageTree.tree_event_name, + {detail: {id: id}} + ) + ); } refreshContents(element) { @@ -330,8 +330,9 @@ class D4SStorageTree extends D4SStorageHtmlElement { fillWithContent(element) { const id = element.getAttribute(D4SStorageTree.dataid_attr); - if (this.#foldersMap[id]) { - this.parseItemsData(element, this.#foldersMap[id]); + const contents = this.getFolderItems(id); + if (contents) { + this.parseItemsData(element, contents); } else { this.d4sWorkspace.listFolder(id).then(data => { this.setFolderItems(id, data); @@ -372,13 +373,8 @@ class D4SStorageTree extends D4SStorageHtmlElement { li.addEventListener('click', (ev) => { ev.stopPropagation(); - this.dispatchEvent( - new CustomEvent( - D4SStorageTree.tree_event_name, - {detail: ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr)} - ) - ); - }) + this.select(ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr)); + }); return li; } @@ -418,8 +414,9 @@ class D4SStorageTree extends D4SStorageHtmlElement { refreshFolder(id) { const folder = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); console.log("Refreshing folder: " + folder.getAttribute(D4SStorageTree.dataname_attr)); + this.setFolderItems(id, null, false); this.refreshContents(folder); - this.select(id); + // this.select(id); } createNewFolderIn(id) { @@ -489,7 +486,8 @@ class D4SStorageFolder extends D4SStorageHtmlElement { } connectedCallback() { - D4SStorageHtmlElement.appendBootstrapStylesheets(this.shadowRoot); + super.connectedCallback(); + const style = document.createElement('style') style.innerHTML = /*css*/` span { @@ -513,21 +511,30 @@ class D4SStorageFolder extends D4SStorageHtmlElement { disconnectedCallback() { const tree = document.querySelector("d4s-storage-tree"); if (tree) { - tree.removeEventListener(D4SStorageTree.tree_event_name); tree.removeEventListener(D4SStorageTree.folder_data_event_name); + tree.removeEventListener(D4SStorageTree.tree_event_name); } } registerOtherListeners(event) { const tree = document.querySelector("d4s-storage-tree"); if (tree) { - tree.addEventListener(D4SStorageTree.tree_event_name, this.treeEventHandler.bind(this)); tree.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler.bind(this)); + tree.addEventListener(D4SStorageTree.tree_event_name, this.treeEventHandler.bind(this)); + } + } + + folderDataEventHandler(event) { + if (event.detail.id === event.target.currentId) { + const folderItems = event.target.getFolderItems(event.detail.id); + if (folderItems) { + this.parseItemsData(folderItems); + } } } treeEventHandler(event) { - const folderItems = event.target.getFolderItems(event.detail); + const folderItems = event.target.getFolderItems(event.detail.id); if (folderItems) { // It's first time folder is selected, data is stil not loaded, // they will be loaded by the folderDataEventHandler() function when available @@ -535,15 +542,6 @@ class D4SStorageFolder extends D4SStorageHtmlElement { } } - folderDataEventHandler(event) { - if (event.detail === event.target.currentId) { - const folderItems = event.target.getFolderItems(event.detail); - if (folderItems) { - this.parseItemsData(folderItems); - } - } - } - createContainer() { var div = document.createElement('div') div.classList.add('d4s-folder') -- 2.17.1 From b939da256c469a8d69eed646548d8605d23fb02d Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Wed, 9 Aug 2023 18:25:25 +0200 Subject: [PATCH 09/11] Beta3: fixed multiple requestes for rapid click in folders with pending requests, fixed double rendering of already received data, added attribute to enable the download of a file selected in folder browser (disabled by default) --- storage/d4s-storage.js | 121 ++++++++++++++++++++++++++++++----------- storage/index.html | 4 +- 2 files changed, 91 insertions(+), 34 deletions(-) diff --git a/storage/d4s-storage.js b/storage/d4s-storage.js index a384523..46e6369 100644 --- a/storage/d4s-storage.js +++ b/storage/d4s-storage.js @@ -111,9 +111,11 @@ class D4SStorageTree extends D4SStorageHtmlElement { #boot = null #baseurl = 'https://api.d4science.org/workspace' + #filedownloadenabled = false; #d4sworkspace = null; #foldersMap = {}; #currentid = null; + #pendingIdRequests = new Set(); constructor() { super() @@ -124,6 +126,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { get boot() { return this.#boot; } + get baseUrl() { return this.#baseurl; } @@ -134,6 +137,14 @@ class D4SStorageTree extends D4SStorageHtmlElement { this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot); } + set fileDownloadEnabled(value) { + this.#filedownloadenabled = value; + } + + get fileDownloadEnabled() { + return this.#filedownloadenabled; + } + get d4sWorkspace() { return this.#d4sworkspace; } @@ -148,6 +159,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { setFolderItems(id, items, fireEvent = true) { this.#foldersMap[id] = items; + this.#pendingIdRequests.delete(id); if (fireEvent) { this.dispatchEvent( new CustomEvent( @@ -166,10 +178,29 @@ class D4SStorageTree extends D4SStorageHtmlElement { this.getFolderItems(this.currentId); } + static get observedAttributes() { + return ["base-url", "file-download-enabled"]; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue !== newValue) { + switch (name) { + case "base-url": + this.baseUrl = newValue; + break; + case "file-download-enabled": + this.fileDownloadEnabled = (newValue.toLowerCase() === 'true'); + break; + default: + console.warn("Unexpected attribute changed: " + name); + } + } + } + connectedCallback() { super.connectedCallback(); - var div = document.createElement('div') + const div = document.createElement('div') div.innerHTML = /*css*/` `; this.shadowRoot.appendChild(div); + const workspaceDIV = document.createElement('div'); + const vreFolderDIV = document.createElement('div'); + div.appendChild(workspaceDIV); + div.appendChild(vreFolderDIV); this.d4sWorkspace.getWorkspace().then(data => { - this.parseItemsData(div, data); + this.parseItemsData(workspaceDIV, data); }).catch(err => console.error(err)); this.d4sWorkspace.getVREFolder().then(data => { - this.parseItemsData(div, data); + this.parseItemsData(vreFolderDIV, data); }).catch(err => console.error(err)) this.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler); @@ -222,9 +260,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { } folderDataEventHandler(event) { - if (event.detail.id === this.currentId) { - this.fillWithContent(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${this.currentId}']`)) - } + this.fillWithContent(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${event.detail.id}']`)) } toolbarEventHandler(event) { @@ -281,8 +317,12 @@ class D4SStorageTree extends D4SStorageHtmlElement { folderEventHandler(event) { switch (event.detail.itemtype) { case D4SStorageFolder.file_selected_detail_itemtype: - console.info("Download of file: " + event.detail.name + " [" + event.detail.id + "]"); - this.d4sWorkspace.download(event.detail.id, event.detail.name).catch(err => {alert(err)}); + if (this.fileDownloadEnabled) { + console.info("Download of file: " + event.detail.name + " [" + event.detail.id + "]"); + this.d4sWorkspace.download(event.detail.id, event.detail.name).catch(err => {alert(err)}); + } else { + console.log("File download is disabled"); + } break; case D4SStorageFolder.folder_selected_detail_itemtype: this.select(event.detail.id); @@ -295,7 +335,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { select(id) { this.currentId = id; - var li = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); + const li = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); this.displayPath(li); if (li.classList.contains(this.#selectedClass)) { @@ -334,16 +374,19 @@ class D4SStorageTree extends D4SStorageHtmlElement { if (contents) { this.parseItemsData(element, contents); } else { - this.d4sWorkspace.listFolder(id).then(data => { - this.setFolderItems(id, data); - }).catch(err => console.error(err)); + if (!this.#pendingIdRequests.has(id)) { + this.#pendingIdRequests.add(id); + this.d4sWorkspace.listFolder(id).then(data => { + this.setFolderItems(id, data); + }).catch(err => console.error(err)); + } } } parseItemsData(parentElement, data) { parentElement.classList.add(this.#loadedClass); const parentId = parentElement.getAttribute(D4SStorageTree.dataid_attr); - var ul = document.createElement('ul'); + const ul = document.createElement('ul'); ul.classList.add('nested'); if (!parentId) { ul.classList.add('root'); @@ -367,7 +410,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { if (parentId) { li.setAttribute(D4SStorageTree.parentid_attr, parentId); } - var child = document.createElement('span'); + const child = document.createElement('span'); child.innerHTML = /*html*/`` + label; li.appendChild(child) @@ -375,11 +418,38 @@ class D4SStorageTree extends D4SStorageHtmlElement { ev.stopPropagation(); this.select(ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr)); }); + // li.addEventListener("dragenter", dee =>{ + // dee.stopPropagation(); + // dee.preventDefault(); + // if (li == dee.target) { + // dee.target.classList.add("dragover"); + // } + // }, false); + // li.addEventListener("dragleave", dle =>{ + // dle.stopPropagation(); + // dle.preventDefault(); + // if (li == dle.target) { + // dle.target.classList.remove("dragover"); + // } + // }, false); + // li.addEventListener("dragover", doe => { + // doe.stopPropagation(); + // doe.preventDefault(); + // }, false); + // li.addEventListener("drop", de => { + // de.stopPropagation(); + // de.preventDefault(); + + // const dt = de.dataTransfer; + // const files = dt.files; + + // console.log(files); + // }, false); return li; } displayPath(li) { - var curr = li + let curr = li while (curr.parentElement != null) { if (curr.parentElement.tagName == 'UL') { curr.parentElement.style.display = 'block' @@ -395,22 +465,6 @@ class D4SStorageTree extends D4SStorageHtmlElement { } } - static get observedAttributes() { - return ["base-url"]; - } - - attributeChangedCallback(name, oldValue, newValue) { - if (oldValue !== newValue) { - switch (name) { - case "base-url": - this.baseUrl = newValue - break - default: - console.warn("Unexpected attribute changed: " + name); - } - } - } - refreshFolder(id) { const folder = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); console.log("Refreshing folder: " + folder.getAttribute(D4SStorageTree.dataname_attr)); @@ -480,7 +534,7 @@ class D4SStorageFolder extends D4SStorageHtmlElement { static file_selected_detail_itemtype = "file_selected"; #selectedbgcolor = 'lightgray' - + constructor() { super() } @@ -997,7 +1051,8 @@ window.customElements.define('d4s-storage-tool', D4SStorageToolbar); /** * Builds storagehub VRE folder or Workspace root tree, linked to toolbar and foldr list by events *
- +
-- 2.17.1 From 7b2040c629bb81f0f878dc1b320358a7fa47a818 Mon Sep 17 00:00:00 2001 From: dcore94 Date: Thu, 5 Oct 2023 18:34:58 +0200 Subject: [PATCH 10/11] optionally show files in tree --- storage/d4s-storage.js | 25 +++++++++++++++++++------ storage/index.html | 3 ++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/storage/d4s-storage.js b/storage/d4s-storage.js index 46e6369..fce3de2 100644 --- a/storage/d4s-storage.js +++ b/storage/d4s-storage.js @@ -112,6 +112,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { #boot = null #baseurl = 'https://api.d4science.org/workspace' #filedownloadenabled = false; + #showfiles = true; #d4sworkspace = null; #foldersMap = {}; #currentid = null; @@ -130,6 +131,14 @@ class D4SStorageTree extends D4SStorageHtmlElement { get baseUrl() { return this.#baseurl; } + + get showFiles() { + return this.#showfiles; + } + + set showFiles(value){ + this.#showfiles = value + } set baseUrl(url) { this.#baseurl = url; @@ -179,7 +188,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { } static get observedAttributes() { - return ["base-url", "file-download-enabled"]; + return ["base-url", "file-download-enabled", "show-files"]; } attributeChangedCallback(name, oldValue, newValue) { @@ -191,6 +200,9 @@ class D4SStorageTree extends D4SStorageHtmlElement { case "file-download-enabled": this.fileDownloadEnabled = (newValue.toLowerCase() === 'true'); break; + case "show-files": + this.showFiles = (newValue.toLowerCase() === 'true'); + break; default: console.warn("Unexpected attribute changed: " + name); } @@ -393,17 +405,17 @@ class D4SStorageTree extends D4SStorageHtmlElement { } if (Array.isArray(data)) { data - .filter(item => item['@class'].includes('FolderItem')) + .filter(item => this.showFiles || item['@class'].includes('FolderItem')) .forEach(item => { - ul.appendChild(this.createListItem(item.name, item.id, parentId)); + ul.appendChild(this.createListItem(item.name, item.id, item['@class'], parentId)); }) } else { - ul.appendChild(this.createListItem(data.displayName ? data.displayName : data.name, data.id, parentId)); + ul.appendChild(this.createListItem(data.displayName ? data.displayName : data.name, data.id, data['@class'], parentId)); } parentElement.appendChild(ul); } - createListItem(label, id, parentId) { + createListItem(label, id, type, parentId) { const li = document.createElement('li'); li.setAttribute(D4SStorageTree.dataname_attr, label); li.setAttribute(D4SStorageTree.dataid_attr, id); @@ -411,7 +423,8 @@ class D4SStorageTree extends D4SStorageHtmlElement { li.setAttribute(D4SStorageTree.parentid_attr, parentId); } const child = document.createElement('span'); - child.innerHTML = /*html*/`` + label; + const icon = type.includes('FolderItem') ? "folder.svg" : "file-earmark.svg" + child.innerHTML = /*html*/`` + label; li.appendChild(child) li.addEventListener('click', (ev) => { diff --git a/storage/index.html b/storage/index.html index ef5348e..5651241 100644 --- a/storage/index.html +++ b/storage/index.html @@ -20,7 +20,8 @@ + file-download-enabled="true" + show-files="false"/>
-- 2.17.1 From e577298d78f6f39d532346d4c00e3639c16741fd Mon Sep 17 00:00:00 2001 From: dcore94 Date: Fri, 6 Oct 2023 10:24:24 +0200 Subject: [PATCH 11/11] added optional support for drag and drop --- storage/d4s-storage.js | 52 +++++++++++++++++++++++++++++++++++++----- storage/index.html | 38 ++++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/storage/d4s-storage.js b/storage/d4s-storage.js index fce3de2..65432c3 100644 --- a/storage/d4s-storage.js +++ b/storage/d4s-storage.js @@ -113,6 +113,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { #baseurl = 'https://api.d4science.org/workspace' #filedownloadenabled = false; #showfiles = true; + #allowdrag = false; #d4sworkspace = null; #foldersMap = {}; #currentid = null; @@ -139,6 +140,14 @@ class D4SStorageTree extends D4SStorageHtmlElement { set showFiles(value){ this.#showfiles = value } + + get allowDrag() { + return this.#allowdrag; + } + + set allowDrag(value){ + this.#allowdrag = value + } set baseUrl(url) { this.#baseurl = url; @@ -188,7 +197,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { } static get observedAttributes() { - return ["base-url", "file-download-enabled", "show-files"]; + return ["base-url", "file-download-enabled", "show-files", "allow-drag"]; } attributeChangedCallback(name, oldValue, newValue) { @@ -203,6 +212,9 @@ class D4SStorageTree extends D4SStorageHtmlElement { case "show-files": this.showFiles = (newValue.toLowerCase() === 'true'); break; + case "allow-drag": + this.allowDrag = (newValue.toLowerCase() === 'true'); + break; default: console.warn("Unexpected attribute changed: " + name); } @@ -215,7 +227,7 @@ class D4SStorageTree extends D4SStorageHtmlElement { const div = document.createElement('div') div.innerHTML = /*css*/`