diff --git a/package.json b/package.json index f6f6ddf..18ea230 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "devDependencies": { "@babel/plugin-transform-private-methods": "^7.24.1", + "@tsconfig/node20": "^20.1.4", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "eslint": "^7.32.0", diff --git a/src/App.vue b/src/App.vue index 0cc456c..7ed59e9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -27,28 +27,25 @@ export default { * B. ... * * General issues: - * I. New pod initialization / old pod re-starting (CSS interface) - * II. the presence of <../> . on User card$.ttl - * III. Web hosting of the application -- github pages + * I. ... * * Data Upload: * 1. a way to designate where a file is uploaded to (i.e. directory structure) - * 2. a drag and drop interface - * 3. a loading icon (to tell when file upload is done) + * 2. a loading icon (to tell when file upload is done) * * Data Browser: - * 4. a Pod data browser -- use new Comunica engine - * 5. designate a way to display the directory structure (along with the files) + * 3. a Pod data browser -- use new Comunica engine + * 4. designate a way to display the directory structure (along with the files) * * Data Query: - * 6. Area to write the query - * 7. Area to designate sources - * 8. submit query button - * 9. streamed output display + * 5. Area to write the query + * 6. Area to designate sources + * 7. submit query button + * 8. streamed output display * * Data Privacy: - * 10. A view of the current privacy - * 11. A way to add/change privacy + * 9. A view of the current privacy + * 10. A way to add/change privacy */ diff --git a/src/components/EditPrivacy.vue b/src/components/EditPrivacy.vue index f08178f..6d2cea7 100644 --- a/src/components/EditPrivacy.vue +++ b/src/components/EditPrivacy.vue @@ -1,29 +1,956 @@ + permissionsString: "", + webId: "", + podList: [], + dirContents: WorkingData, + containerContents: WorkingData, + hasAcl: null, + cannotMakeAcl: false, + currentLocation: "", + currentUrl: null, + urls: [], + containerUrls: [], + resourceUrls: [], + inContainer: WorkingData, + newUrls: [], + aclUrl: "", + hasAccess: [], + }; + }, + methods: { + /* + Keeps the filter menu open while toggling the viewing options + */ + keepMenuOpen() { + this.filterMenuClosed = false; // Keep the menu open after clicking an item + }, + /* + Checks if the input item url is a container + */ + containerCheck(itemUrl) { + return itemUrl.endsWith("/"); + }, + + /** + * method that returns a list of child container URLs from within a specified parent container + * + * @param currentDir the current container from which child containers should be identified + * @param contUrlList the list of containers in the current directory + */ + childContainers(currentDir, contUrlList) { + const newUrlLst = contUrlList + .filter((url) => url !== currentDir) // Remove the current parent container + .map((url) => { + const segments = url + .split("/") + .filter((segment) => segment.length > 0); + return segments[segments.length - 1] + "/"; + }); + // for navigating up a directory path (not possible when in root directory) + if (currentDir !== this.podList[0]) { + newUrlLst.push("/.."); + } + return newUrlLst.sort((a, b) => a.length - b.length); + }, + + /** + * Similar logic as the childContainers method but for resources + * + * @param currentDir the current container from which child resources should be identified + * @param rescUrlList the current container from which child resources should be identified + */ + childResources(currentDir, rescUrlList) { + const newUrlLst = rescUrlList + .filter((url) => url !== currentDir) // Remove the current parent container + .map((url) => { + const segments = url + .split("/") + .filter((segment) => segment.length > 0); + return segments[segments.length - 1]; + }); + return newUrlLst.sort((a, b) => a.length - b.length); + }, + + /** + * method that allows for the traversal of the container structure in a User's Pod + * + * @param aNewLocation the container name that a user will be traversing to + */ + async changeCurrentLocation(aNewLocation) { + const dismembered = this.currentLocation.split("//"); + const segments = dismembered[1] + .split("/") + .filter((segment) => segment.length > 0); + + // for moving 'up' the container levels (toward the root) + if (aNewLocation === "/..") { + segments.pop(); + const newUrl = dismembered[0] + "//" + segments.join("/") + "/"; + this.currentLocation = newUrl; + await this.getSpecificData(newUrl); + this.currentUrl = null; + } + // for moving 'down' the container levels (away from the root) + else { + const newUrl = + dismembered[0] + "//" + segments.join("/") + "/" + aNewLocation; + this.currentLocation = newUrl; + await this.getSpecificData(newUrl); + this.currentUrl = null; + } + + /** + * method for copying text to the user's clipboard + * + * @param text the text to be coppied + */ + }, + copyText(text) { + navigator.clipboard.writeText(text); + }, + + /* + Two methods for controlling the UI + */ + toggleShared(index) { + if (this.showSharedIndex === index) { + this.showSharedIndex = null; // Hide the form if it's already shown + } else { + this.showSharedIndex = index; // Show the form for the clicked item + } + }, + toggleForm(index) { + if (this.showFormIndex === index) { + this.showFormIndex = null; // Hide the form if it's already shown + } else { + this.showFormIndex = index; // Show the form for the clicked item + } + }, + + /** + * method that submits the alterations to the specified .acl permissions file + * + * @param url the URL of the container that .acl changes are being made to + */ + async submitForm(url) { + // Check if entered user WebId is a valid URL + this.userUrlInvalid = checkUrl(this.userUrl, this.webId); + + // Handle permissions permutation logic + if (this.permissions.read) { + this.permissionsString += "Read / "; + } + if (this.permissions.write) { + this.permissions.append = true; + this.permissionsString += "Write / "; + } + if (this.permissions.append && !this.permissions.write) { + this.permissionsString += "Append / "; + } + if (this.permissions.read && this.permissions.write) { + this.permissions.control = true; + this.permissionsString = "Control (Read & Write) / "; + } + if (this.permissionsString === "") { + this.permissionsString = "No / "; + } + + // Call function to do the .acl changing (only if the added webID URL is valid) + if (!this.userUrlInvalid) { + // actual .acl changing + await changeAcl(url, this.userUrl, this.permissions); + + // Message that tells the changes that have been made + const ex = [ + this.permissionsString.length - 3, + this.permissionsString.length - 2, + this.permissionsString.length - 1, + ]; + this.permissionsString = this.permissionsString + .split("") + .filter((char, index) => !ex.includes(index)) + .join(""); + this.submissionDone = true; + } + }, + + /** + * Resets Form variables after a successful submission + */ + clearForm() { + this.userUrl = ""; + this.permissionsString = ""; + this.permissions = { + read: false, + write: false, + append: false, + control: false, + }; + this.submissionDone = false; + }, + clearPermissionString() { + this.permissionsString = ""; + this.submissionDone = false; + }, + + /** + * Sorts container URLs and resource URLs into different lists + */ + separateUrls() { + this.containerUrls = this.urls.filter((url) => url.endsWith("/")); + this.resourceUrls = this.urls.filter((url) => !url.endsWith("/")); + if ( + this.currentLocation === this.podList[0] && + !this.urls.includes(this.podList[0]) + ) { + this.urls.push(this.podList[0]); + this.containerUrls.push(this.podList[0]); + } + this.urls = this.urls.sort((a, b) => a.length - b.length); + this.container = this.urls.sort((a, b) => a.length - b.length); + this.resourceUrls = this.urls.sort((a, b) => a.length - b.length); + }, + + /** + * Calls getPodURLs() from fileUpload.ts to obtain a list of pods from the logged-in user's webID. + * Obtains 'pod' variable (URL path to user's Pod). + */ + async podURL() { + this.webId = currentWebId(); + this.podList = await getPodURLs(); + this.currentLocation = this.podList[0]; // assuming that the user only has one pod at the moment... + }, + + /** + * Obtains the containers within the root directory of a user's pod, + * puts the URLs for these containers into an array, + * then sorts the array to reflect heirarchy + */ + async getGeneralData() { + this.dirContents = await fetchData(this.podList[0]); + this.urls = getContainedResourceUrlAll(this.dirContents); + this.separateUrls(); + }, + + /** + * Obtains a list containers and/or resources located in the provided container + * + * @param path the URL of the container for which access rights are being displayed + */ + async getSpecificData(path) { + this.dirContents = await fetchData(path); + this.urls = getContainedResourceUrlAll(this.dirContents); + this.separateUrls(); + this.hasAccess = await fetchAclAgents(path); + }, + + /** + * Obtains a list of agents that have access to the designated resource or container + * + * @param path the URL of the resource or container for which access rights are to be displayed + */ + async getSpecificAclData(path) { + this.hasAcl = await fetchPermissionsData(path); // value is either .acl obj OR null (if .acl does not exist) + if (this.hasAcl !== null) { + this.hasAccess = await fetchAclAgents(path); + this.cannotMakeAcl = false; + } + }, + + /** + * Makes a new .acl file for containers or resources that do not have a vaild .acl + * + * @param path the URL of the resource or container for which an .acl is to be made + */ + async makeNewAcl(path) { + try { + await generateAcl(path, this.webId); + await this.getSpecificAclData(path); + } catch (err) { + console.error(err); + this.cannotMakeAcl = true; + } + }, + }, + mounted() { + // Delays the execution of these functions on page reload (to avoid async-related errors) + this.podURL(); + setTimeout(() => { + this.getGeneralData(); + }, 500); + }, +}; + + + diff --git a/src/components/PodBrowser.vue b/src/components/PodBrowser.vue index b1345ee..aed54a1 100644 --- a/src/components/PodBrowser.vue +++ b/src/components/PodBrowser.vue @@ -1,36 +1,46 @@