var D4S_STORAGE_SCRIPT = document.currentScript /** * Base class of and */ class D4SStorageHtmlElement extends HTMLElement { #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'}) } connectedCallback() { } get srcBaseURL() { return this.#srcbaseurl; } } class D4SStorageToolbar extends D4SStorageHtmlElement { 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() } connectedCallback() { 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
`; 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: action}})); } get lastAction() { return this.#last_action; } } class D4SStorageTree extends D4SStorageHtmlElement { static tree_event_name = "d4s-tree"; 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' #filedownloadenabled = false; #showfiles = true; #allowdrag = false; #d4sworkspace = null; #foldersMap = {}; #currentid = null; #pendingIdRequests = new Set(); constructor() { super() this.#boot = document.querySelector('d4s-boot-2'); this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot); } get boot() { return this.#boot; } get baseUrl() { return this.#baseurl; } get showFiles() { return this.#showfiles; } set showFiles(value){ this.#showfiles = value } get allowDrag() { return this.#allowdrag; } set allowDrag(value){ this.#allowdrag = value } set baseUrl(url) { this.#baseurl = url; // this.setAttribute("baseurl", url) this.#d4sworkspace = new D4SWorkspace(this.#baseurl, this.#boot); } set fileDownloadEnabled(value) { this.#filedownloadenabled = value; } get fileDownloadEnabled() { return this.#filedownloadenabled; } get d4sWorkspace() { return this.#d4sworkspace; } set currentId(id) { this.#currentid = id; } get currentId() { return this.#currentid; } setFolderItems(id, items, fireEvent = true) { this.#foldersMap[id] = items; this.#pendingIdRequests.delete(id); if (fireEvent) { this.dispatchEvent( new CustomEvent( D4SStorageTree.folder_data_event_name, {detail: {id: id}} ) ); } } getFolderItems(id) { return this.#foldersMap[id]; } getSelectedFolderItems() { this.getFolderItems(this.currentId); } static get observedAttributes() { return ["base-url", "file-download-enabled", "show-files", "allow-drag"]; } 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; 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); } } } connectedCallback() { super.connectedCallback(); 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(workspaceDIV, data); }).catch(err => console.error(err)); this.d4sWorkspace.getVREFolder().then(data => { this.parseItemsData(vreFolderDIV, data); }).catch(err => console.error(err)) this.addEventListener(D4SStorageTree.folder_data_event_name, this.folderDataEventHandler); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", this.registerOtherListeners.bind(this)); } } disconnectedCallback() { 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); } 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)); } folderDataEventHandler(event) { this.fillWithContent(this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${event.detail.id}']`)) } toolbarEventHandler(event) { switch (event.detail.action) { 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.detail.action); break; } } folderEventHandler(event) { switch (event.detail.itemtype) { case D4SStorageFolder.file_selected_detail_itemtype: 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); break; default: console.warn("Unexpected folder event: " + event.detail.itemtype); break; } } select(id) { this.currentId = id; const li = this.shadowRoot.querySelector(`*[${D4SStorageTree.dataid_attr}='${id}']`); this.displayPath(li); if (li.classList.contains(this.#selectedClass)) { this.toggleULDisplay(li); } // switch selected and lists folder contents if (!li.classList.contains(this.#selectedClass)) { const selected = this.shadowRoot.querySelector('.' + this.#selectedClass) if (selected) { selected.classList.remove(this.#selectedClass) } li.classList.add(this.#selectedClass) } if (!li.classList.contains(this.#loadedClass)) { this.fillWithContent(li); } this.dispatchEvent( new CustomEvent( D4SStorageTree.tree_event_name, {detail: {id: id, bubbles: true}} ) ); } refreshContents(element) { [...element.querySelectorAll(`ul`)].forEach(ul => { ul.remove(); }); this.fillWithContent(element); } fillWithContent(element) { const id = element.getAttribute(D4SStorageTree.dataid_attr); const contents = this.getFolderItems(id); if (contents) { this.parseItemsData(element, contents); } else { 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); const ul = document.createElement('ul'); ul.classList.add('nested'); if (!parentId) { ul.classList.add('root'); } if (Array.isArray(data)) { data .filter(item => this.showFiles || item['@class'].includes('FolderItem')) .forEach(item => { ul.appendChild(this.createListItem(item, parentId)); }) } else { ul.appendChild(this.createListItem(data, parentId)); } parentElement.appendChild(ul); } getIconByMIME(mime){ if(mime.startsWith("image")) return "image"; if(mime.startsWith("application/pdf")) return "picture_as_pdf"; if(mime.startsWith("application/zip")) return "archive"; if(mime.startsWith("application/xml")) return "code"; if(mime.match(/tar|compressed|gzip|gz|tgz/)) return 'archive'; return null; } getIconByExtension(name){ const tks = name.split(".") if(tks.length === 0) return null; const ext = tks[tks.length - 1] if(ext.match(/java$|py$|^r$|^c$|ccp|lua|jl|^sh$|json|xml|xsl|xslt|md$|xq$|xqm$/i)) return "code"; if(ext.match(/js$/i)) return "javascript"; if(ext.match(/html$|css$|php$/i)) return ext; if(ext.match(/kml$/i)) return "place"; if(ext.match(/ics$/i)) return "event"; if(ext.match(/csv$|xls$|ods$/i)) return "assessment"; if(ext.match(/txt$|odt$|rtf$|doc$|docx$/i)) return "description"; if(ext.match(/ppt$|odp$/i)) return "slideshow"; return null; } createListItem(item, parentId) { const label = item.displayName ? item.displayName : item.title const id = item.id const type = item["@class"] const mime = item.content && item.content.mimeType ? item.content.mimeType : null const li = document.createElement('li'); li.setAttribute(D4SStorageTree.dataname_attr, label); li.setAttribute(D4SStorageTree.dataid_attr, id); li.style.userSelect = "none" li.title = li.alt = label if (parentId) { li.setAttribute(D4SStorageTree.parentid_attr, parentId); } //const icon = type.includes('FolderItem') || type.includes("SharedFolder")? "folder.svg" : "file-earmark.svg" var icon = "insert_drive_file"; if(type.includes('FolderItem')){ icon = "folder" }else if(type.includes("SharedFolder")){ icon = "folder_shared" }else{ const im = mime ? this.getIconByMIME(mime) : null if(im) icon = im; else{ const ie = this.getIconByExtension(label) if(ie) icon = ie; } } li.innerHTML = `
${icon} ${label}
` li.addEventListener('click', (ev) => { ev.stopPropagation(); ev.preventDefault() this.select(ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr)); }); if(this.allowDrag){ li.setAttribute("draggable", true) li.addEventListener("dragstart", ev=>{ const id = ev.currentTarget.getAttribute(D4SStorageTree.dataid_attr) ev.dataTransfer.effectAllowed = 'copy' ev.dataTransfer.setData('text/html', ev.currentTarget.querySelector("span").innerHTML) ev.dataTransfer.setData('text/plain+publiclink', this.#d4sworkspace.getPublicLink(id)) ev.dataTransfer.setData('text/plain+downloadlink', this.#d4sworkspace.getDownloadLink(id)) ev.stopPropagation() }) li.addEventListener("dragend", ev=>{ ev.preventDefault() ev.stopPropagation() }) } // 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) { let curr = li while (curr.parentElement != null) { if (curr.parentElement.tagName == 'UL') { curr.parentElement.style.display = 'block' } curr = curr.parentElement } } toggleULDisplay(li) { const ul = li.querySelector('ul') if (ul) { ul.style.display = (ul.style.display === 'none') ? 'block' : 'none' } } 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); } createNewFolderIn(id) { const newFolderName = window.prompt("Enter the new folder name"); if (newFolderName) { this.d4sWorkspace.getOrCreateFolder(id, newFolderName, "Created from JS UI").then(newId => { 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() { super.connectedCallback(); const style = document.createElement('style') style.innerHTML = /*css*/` span { cursor: pointer; user-select: none; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; } ` this.shadowRoot.appendChild(style); this.createContainer(); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", this.registerOtherListeners.bind(this)); } } disconnectedCallback() { const tree = document.querySelector("d4s-storage-tree"); if (tree) { 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.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.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 this.parseItemsData(folderItems); } } createContainer() { var div = document.createElement('div') div.classList.add('d4s-folder') var child = this.shadowRoot.querySelector('.d4s-folder') if (child) { this.shadowRoot.removeChild(child) } this.shadowRoot.appendChild(div) return div } parseItemsData(data) { const root = this.createContainer(); if (Array.isArray(data)) { data.forEach(item => root.appendChild(this.createFileRow(item))); } else { console.error("Returned data is not an array: " + data); } } createFileRow(item) { var div = document.createElement('div') div.classList.add('row') var filename = document.createElement('div') filename.classList.add('col') var spanAndName = /*html*/`${item.name}` filename.innerHTML = this.iconTag(item) + spanAndName const isFolder = item['@class'].includes('FolderItem') filename.addEventListener('click', (ev) => { ev.stopPropagation() ev.preventDefault() if (isFolder) { const span = ev.currentTarget.querySelector(':nth-child(2)') if (span) { span.style = 'background-color: ' + this.#selectedbgcolor } this.dispatchEvent( new CustomEvent( D4SStorageFolder.selected_event_name, {detail: {itemtype: D4SStorageFolder.folder_selected_detail_itemtype, id: item.id, name: item.name}} ) ); } else { this.dispatchEvent( new CustomEvent( D4SStorageFolder.selected_event_name, {detail: {itemtype: D4SStorageFolder.file_selected_detail_itemtype, id: item.id, name: item.name}} ) ); } }) div.appendChild(filename) return div } iconTag(item) { var i = /*html*/`` if (item['@class'].includes('FolderItem')) { i = /*html*/`` } else if (item['@class'].includes('ImageFile')) { i = /*html*/`` } else if (item['@class'].includes('PDFFileItem')) { i = /*html*/`` } return '' + i + '' } } /** * 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; } getPublicLink(itemId){ return this.#workspaceURL + "/items/" + itemId + "/publiclink" } getDownloadLink(itemId){ return this.#workspaceURL + "/items/" + itemId + "/download" } /** * 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 Blob() promise * @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) { 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(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); 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?exclude=hl:accounting"; return this.#callWorkspace("GET", uri, null, null, extraHeaders) .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; }); } /** * 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(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); 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(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 }); } }); }); }; /** * 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", "/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, as a Blob object * @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(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 * @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, 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(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 * @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 = "/items/" + itemId + "/publiclink"; if (version) { uri += "version=" + version; } return this.#callWorkspace("GET", uri, null, null, extraHeaders) .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); 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(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; }); } } /** * 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);