1163 lines
38 KiB
JavaScript
1163 lines
38 KiB
JavaScript
var D4S_STORAGE_SCRIPT = document.currentScript
|
|
|
|
/**
|
|
* Base class of <d4s-storage-tree> and <d4s-storage-folder>
|
|
*/
|
|
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*/`
|
|
<div class="border">
|
|
<span class="px-2" id="refresh"><img src="${this.srcBaseURL}/img/arrow-clockwise.svg" alt="Refresh folder contents"></img></span>
|
|
<span class="px-2" id="create"><img src="${this.srcBaseURL}/img/folder-plus.svg" alt="Create new folder"></img></span>
|
|
<span class="px-2" id="delete"><img src="${this.srcBaseURL}/img/folder-x.svg" alt="Delete folder"></img></span>
|
|
<span class="px-2" id="download"><img src="${this.srcBaseURL}/img/box-arrow-down.svg" alt="Download folder as ZIP"></img></span>
|
|
<span class="px-2" id="upload-file"><img src="${this.srcBaseURL}/img/box-arrow-in-up.svg" alt="Upload file"></img></span>
|
|
<span class="px-2" id="upload-archive"><img src="${this.srcBaseURL}/img/file-earmark-zip.svg" alt="Upload archive"></img></span>
|
|
</div>`;
|
|
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*/`
|
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
|
<style>
|
|
.selected > .li-title {
|
|
background-color: ${this.#selectedbgcolor};
|
|
}
|
|
ul.root {
|
|
padding-left: 0;
|
|
}
|
|
ul, li {
|
|
list-style-type: none;
|
|
margin-bottom: 0;
|
|
padding-left: 0.6em;
|
|
}
|
|
li{
|
|
color: #444444;
|
|
}
|
|
li:hover{
|
|
color: #555555;
|
|
}
|
|
li {
|
|
cursor: pointer;
|
|
}
|
|
.dragover {
|
|
background-color: purple;
|
|
}
|
|
</style>`;
|
|
|
|
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 = `
|
|
<div class="li-title d-flex gap-1">
|
|
<span class="material-icons">${icon}</span>
|
|
<span>${label}</span>
|
|
</div>
|
|
`
|
|
|
|
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*/`<span>${item.name}</span>`
|
|
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*/`<img src="${this.srcBaseURL}/img/file-earmark.svg"></img>`
|
|
if (item['@class'].includes('FolderItem')) {
|
|
i = /*html*/`<img src="${this.srcBaseURL}/img/folder.svg"></img>`
|
|
} else if (item['@class'].includes('ImageFile')) {
|
|
i = /*html*/`<img src="${this.srcBaseURL}/img/image.svg"></img>`
|
|
} else if (item['@class'].includes('PDFFileItem')) {
|
|
i = /*html*/`<img src="${this.srcBaseURL}/img/filetype-pdf.svg"></img>`
|
|
}
|
|
return '<span class="px-2">' + i + '</span>'
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 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 <code>querySelector()</code> 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 <code>FormData</code> object you have to leave the <code>mime</code> attribute as <code>null</code>.
|
|
* @param {*} method the method to use for the call, default to <code>GET</code>
|
|
* @param {*} uri the uri to invoke, relative to <code>workspaceURL</code> 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 <code>true</code> if the file has been correctly created, <code>false<c/ode> 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 <code>true</code> 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 <code>true</code> if the file has been correctly created, <code>false<c/ode> 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 <code>true</code> if the file has been correctly created, <code>false<c/ode> 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
|
|
* <d4s-storage-tool>
|
|
*/
|
|
window.customElements.define('d4s-storage-tool', D4SStorageToolbar);
|
|
|
|
/**
|
|
* Builds storagehub VRE folder or Workspace root tree, linked to toolbar and foldr list by events
|
|
* <d4s-storage-tree
|
|
* /@base-url: the base endpoint url string [defaults to: https://api.d4science.org/workspace]
|
|
* /@file-download-enabled: true/false to enable/disable the file download on d4s-storage-tool click event (false by default)
|
|
*/
|
|
window.customElements.define('d4s-storage-tree', D4SStorageTree);
|
|
/**
|
|
* Lists elements in selected folder linked to tree and model by events
|
|
* <d4s-storage-folder>
|
|
*/
|
|
window.customElements.define('d4s-storage-folder', D4SStorageFolder);
|