diff --git a/HISTORY.md b/HISTORY.md index db6716e4..f2856aa9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,67 +1,239 @@ -### caMicroscope [?.?.?](https://github.com/camicroscope/camicroscope/compare/v3.7.3...camicroscope:develop) -###### ????-??-?? +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## Index +* **Version 3** + * [3.7.x](#camicroscope-373) + * [3.6.x](#camicroscope-362) + * [3.5.x](#camicroscope-3510) + * [3.4.x](#camicroscope-343) + * [3.3.x](#camicroscope-334) + * [3.2.x](#camicroscope-322) + * [3.1.x](#camicroscope-311) + * [3.0.x](#camicroscope-300) +* **Version 2** + * [2.0.x](#camicroscope-201) +* **Version 1** + * [1.0.x](#camicroscope-10) + +### caMicroscope [Unreleased](https://github.com/camicroscope/camicroscope/compare/v3.7.4...camicroscope:develop) +###### PRESENT + + +### caMicroscope [3.7.4](https://github.com/camicroscope/camicroscope/compare/v3.7.3...camicroscope:v3.7.4) +###### 2020-05-15 +* Add download button to viewer ([#392](https://github.com/camicroscope/caMicroscope/pull/392)) +* Expand HISTORY.md ([#396](https://github.com/camicroscope/caMicroscope/pull/396)) +* Split out large JS sections from table and signup +* Move getUserPermissions to store ### caMicroscope [3.7.3](https://github.com/camicroscope/camicroscope/compare/v3.7.2...camicroscope:v3.7.3) ###### 2020-05-01 * Start this changelog. -* Use Friendlier Alerts in Viewer (#383) -* Batch Slide Loader (#385, #389) -* Filter Selector in Tables (#391) +* Use Friendlier Alerts in Viewer ([#383](https://github.com/camicroscope/caMicroscope/pull/383)) +* Batch Slide Loader ([#385](https://github.com/camicroscope/caMicroscope/pull/385), [#389](https://github.com/camicroscope/caMicroscope/pull/389)) +* Filter Selector in Tables ([#391](https://github.com/camicroscope/caMicroscope/pull/391)) * High-volume Render Optimization -* Temporarily Disabled Security Integration (#388) -* Readme Visual Documentation (#394) -* Details Button in Information Dashboard (#393) +* Temporarily Disabled Security Integration ([#388](https://github.com/camicroscope/caMicroscope/pull/388)) +* Readme Visual Documentation ([#394](https://github.com/camicroscope/caMicroscope/pull/394)) +* Details Button in Information Dashboard ([#393](https://github.com/camicroscope/caMicroscope/pull/393)) ### caMicroscope [3.7.2](https://github.com/camicroscope/camicroscope/compare/v3.7.1...camicroscope:v3.7.2) ###### 2020-04-17 -* Documentation Improvements (#314, #315) -* User Creation Workflow (#371) -* Slide Deletion Workflow (#303, #367, #369) -* Table and Loader Improvements (#340, #345, #356, #358) -* Model and Segment App Improvements (#317, #375 #327, #330, #348, #351, #354, #360, #362, #382) -* Heatmap Color Changes (#322, #381) -* Viewer Tool Tour (#334) +* Documentation Improvements ([#314](https://github.com/camicroscope/caMicroscope/pull/314), [#315](https://github.com/camicroscope/caMicroscope/pull/315)) +* User Creation Workflow ([#371](https://github.com/camicroscope/caMicroscope/pull/371)) +* Slide Deletion Workflow ([#303](https://github.com/camicroscope/caMicroscope/pull/303), [#367](https://github.com/camicroscope/caMicroscope/pull/367), [#369](https://github.com/camicroscope/caMicroscope/pull/369)) +* Table and Loader Improvements ([#340](https://github.com/camicroscope/caMicroscope/pull/340), [#345](https://github.com/camicroscope/caMicroscope/pull/345), [#356](https://github.com/camicroscope/caMicroscope/pull/356), [#358](https://github.com/camicroscope/caMicroscope/pull/358)) +* Model and Segment App Improvements ([#317](https://github.com/camicroscope/caMicroscope/pull/317), [#375](https://github.com/camicroscope/caMicroscope/pull/375), [#327](https://github.com/camicroscope/caMicroscope/pull/327), [#330](https://github.com/camicroscope/caMicroscope/pull/330), [#348](https://github.com/camicroscope/caMicroscope/pull/348), [#351](https://github.com/camicroscope/caMicroscope/pull/351), [#354](https://github.com/camicroscope/caMicroscope/pull/354), [#360](https://github.com/camicroscope/caMicroscope/pull/360), [#362](https://github.com/camicroscope/caMicroscope/pull/362), [#382](https://github.com/camicroscope/caMicroscope/pull/382)) +* Heatmap Color Changes ([#322](https://github.com/camicroscope/caMicroscope/pull/322), [#381](https://github.com/camicroscope/caMicroscope/pull/381)) +* Viewer Tool Tour ([#334](https://github.com/camicroscope/caMicroscope/pull/334)) ### caMicroscope [3.7.1](https://github.com/camicroscope/camicroscope/compare/v3.7.0...camicroscope:v3.7.1) ###### 2020-04-03 * Bugfix: POST instead of UPDATE tables -* Sanitize user input (#301) -* Responsive tables (#306, #308) -* Style: camel case checks (#278) +* Sanitize user input ([#301](https://github.com/camicroscope/caMicroscope/pull/301)) +* Responsive tables ([#306](https://github.com/camicroscope/caMicroscope/pull/306), [#308](https://github.com/camicroscope/caMicroscope/pull/308)) +* Style: camel case checks ([#278](https://github.com/camicroscope/caMicroscope/pull/278)) ### caMicroscope [3.7.0](https://github.com/camicroscope/camicroscope/compare/v3.6.2...camicroscope:v3.7.0) ###### 2020-04-02 -* Use new backend (#291, https://github.com/camicroscope/Caracal/) -* Adopted a code style guide (#270, #282, #281) -* Table/loader improvements (#273, #276) -* Faster UI transitions (#284, #288) -* Spyglass Behavior Improvements (#275, #297) +* Use new backend ([#291](https://github.com/camicroscope/caMicroscope/pull/291), [camicroscope/Caracal](https://github.com/camicroscope/Caracal/)) +* Adopted a code style guide ([#270](https://github.com/camicroscope/caMicroscope/pull/270), [#282](https://github.com/camicroscope/caMicroscope/pull/282), [#281](https://github.com/camicroscope/caMicroscope/pull/281)) +* Table/loader improvements ([#273](https://github.com/camicroscope/caMicroscope/pull/273), [#276](https://github.com/camicroscope/caMicroscope/pull/276)) +* Faster UI transitions ([#284](https://github.com/camicroscope/caMicroscope/pull/284), [#288](https://github.com/camicroscope/caMicroscope/pull/288)) +* Spyglass Behavior Improvements ([#275](https://github.com/camicroscope/caMicroscope/pull/275), [#297](https://github.com/camicroscope/caMicroscope/pull/297)) ### caMicroscope [3.6.2](https://github.com/camicroscope/camicroscope/compare/v3.6.1...camicroscope:v3.6.2) ###### 2020-03-29 -* Add Custom colors for heatmap (#253) -* Model Summary for Segmentation and Prediction Apps (#255, #250) -* Keyboard shortcut upgrades (#262, #265) -* Segmentation Memory Cleanup (#261) -* Edit Prediction Classes (#263) -* Loader Slide Name Checking (#266) -* Panel Text Wrapping (#264) -* Selector Fix #260 +* Add Custom colors for heatmap ([#253](https://github.com/camicroscope/caMicroscope/pull/253)) +* Model Summary for Segmentation and Prediction Apps ([#255](https://github.com/camicroscope/caMicroscope/pull/255), [#250](https://github.com/camicroscope/caMicroscope/pull/250)) +* Keyboard shortcut upgrades ([#262](https://github.com/camicroscope/caMicroscope/pull/262), [#265](https://github.com/camicroscope/caMicroscope/pull/265)) +* Segmentation Memory Cleanup ([#261](https://github.com/camicroscope/caMicroscope/pull/261)) +* Edit Prediction Classes ([#263](https://github.com/camicroscope/caMicroscope/pull/263)) +* Loader Slide Name Checking ([#266](https://github.com/camicroscope/caMicroscope/pull/266)) +* Panel Text Wrapping ([#264](https://github.com/camicroscope/caMicroscope/pull/264)) +* Selector Fix ([#260](https://github.com/camicroscope/caMicroscope/pull/260)) ### caMicroscope [3.6.1](https://github.com/camicroscope/camicroscope/compare/v3.6.0...camicroscope:v3.6.1) ###### 2020-03-26 -* Bugfix: Upload button with no slides (#249) +* Bugfix: Upload button with no slides ([#249](https://github.com/camicroscope/caMicroscope/pull/249)) * Testing Updates -* UI Improvements (#256, #252, #251, #246) +* UI Improvements ([#256](https://github.com/camicroscope/caMicroscope/pull/256), [#252](https://github.com/camicroscope/caMicroscope/pull/252), [#251](https://github.com/camicroscope/caMicroscope/pull/251), [#246](https://github.com/camicroscope/caMicroscope/pull/246)) ### caMicroscope [3.6.0](https://github.com/camicroscope/camicroscope/compare/v3.5.10...camicroscope:v3.6.0) ###### 2020-03-22 -* Removal of Package System (breaking) (#243) -* Table, Slide Loader, and Signup UX Improvements (#239, #241, #227, #226) -* Model Improvements (#223, #231) +* Removal of Package System (breaking) ([#243](https://github.com/camicroscope/caMicroscope/pull/243)) +* Table, Slide Loader, and Signup UX Improvements ([#239](https://github.com/camicroscope/caMicroscope/pull/239), [#241](https://github.com/camicroscope/caMicroscope/pull/241), [#227](https://github.com/camicroscope/caMicroscope/pull/227), [#226](https://github.com/camicroscope/caMicroscope/pull/226)) +* Model Improvements ([#223](https://github.com/camicroscope/caMicroscope/pull/223), [#231](https://github.com/camicroscope/caMicroscope/pull/231)) + + +### caMicroscope [3.5.10](https://github.com/camicroscope/camicroscope/compare/v3.5.9...camicroscope:v3.5.10) +###### 2019-11-13 +* Bugfix: annotation wouldn't draw in certain contexts + + +### caMicroscope [3.5.9](https://github.com/camicroscope/camicroscope/compare/v3.5.8...camicroscope:v3.5.9) +###### 2019-11-11 +* Configurable brush data +* Check pathdb for auth precheck before asking user to login + + +### caMicroscope [3.5.8](https://github.com/camicroscope/camicroscope/compare/v3.5.7...camicroscope:v3.5.8) +###### 2019-11-07 +* Brush Improvements +* Heatmap Items +* User and time info in annots + + +### caMicroscope [3.5.7](https://github.com/camicroscope/camicroscope/compare/v3.5.6...camicroscope:v3.5.7) +###### 2019-10-30 +* User Action Logging + + +### caMicroscope [3.5.6](https://github.com/camicroscope/camicroscope/compare/v3.5.5...camicroscope:v3.5.6) +###### 2019-10-24 +* Heatmap and Labeling Option Additions + + +### caMicroscope [3.5.2](https://github.com/camicroscope/camicroscope/compare/v3.5.1...camicroscope:v3.5.2) +###### 2019-10-09 +* Mpp and Delete Fixes; Drawing Indicator Box Style Change + + +### caMicroscope [3.5.1](https://github.com/camicroscope/camicroscope/compare/v3.4.3...camicroscope:v3.5.1) +###### 2019-09-10 +* Pathdb Hotfix +* Some ml model tool updates + + +### caMicroscope [3.4.3](https://github.com/camicroscope/camicroscope/compare/v3.4.2...camicroscope:v3.4.3) +###### 2019-08-20 +* Updated touch and zoom event fixes. + + +### caMicroscope [3.4.2](https://github.com/camicroscope/camicroscope/compare/v3.4.1...camicroscope:v3.4.2) +###### 2019-07-16 +* Some mobile touch event support, if spotty. +* Changes to max zoom to avoid confusion + + +### caMicroscope [3.4.1](https://github.com/camicroscope/camicroscope/compare/v3.4.0...camicroscope:v3.4.1) +###### 2019-07-03 +* Bufgix: Pathdb Store and Viewer Mode Toggle + + +### caMicroscope [3.4.0](https://github.com/camicroscope/camicroscope/compare/v3.3.4...camicroscope:v3.4.0) +###### 2019-06-22 +* ML pathology in Browser +* Zoom and SBS customization +* Pathdb package simplification + + +### caMicroscope [3.3.4](https://github.com/camicroscope/camicroscope/compare/v3.3.3...camicroscope:v3.3.4) +###### 2019-06-03 +* Heatmap View Integration, HeSep, and PathDB Field Changes + + +### caMicroscope [3.3.3](https://github.com/camicroscope/camicroscope/compare/v3.3.2...camicroscope:v3.3.3) +###### 2019-05-24 +* Bugfix Release (Fuzz Numeric and 2.X style annot detail) + + +### caMicroscope [3.3.2](https://github.com/camicroscope/camicroscope/compare/v3.3.1...camicroscope:v3.3.2) +###### 2019-05-10 +* H&E +* Bugfixes (pathdb home button, heatmap edit unset value, human annot view/pencil) + + +### caMicroscope [3.3.1](https://github.com/camicroscope/camicroscope/compare/v3.3.0...camicroscope:v3.3.1) +###### 2019-05-06 +* Heatmap Editor (with Threshold) +* Login and auth check helpers +* Segmentation App Fixes +* Loader Improvements by [@pranavbudhwant](https://github.com/pranavbudhwant) + + +### caMicroscope [3.2.2](https://github.com/camicroscope/camicroscope/compare/v3.2.1...camicroscope:v3.2.2) +###### 2019-04-02 +* Segmentation App Fixes +* Default Packages "Map Warning" Fix + + +### caMicroscope [3.2.1](https://github.com/camicroscope/camicroscope/compare/v3.2.0...camicroscope:v3.2.1) +###### 2019-03-30 +* Uncomment Segmentation/Counter App Button + + +### caMicroscope [3.2.0](https://github.com/camicroscope/camicroscope/compare/v3.1.1...camicroscope:v3.2.0) +###### 2019-03-30 +* Heatmap and Cell Counter Support +* Pathdb integration + + +### caMicroscope [3.1.1](https://github.com/camicroscope/camicroscope/compare/v3.1.0...camicroscope:v3.1.1) +###### 2019-03-14 +* Fix Table Js includes which were stopping the table from rendering. + + +### caMicroscope [3.1.0](https://github.com/camicroscope/camicroscope/compare/v3.0.0...camicroscope:v3.1.0) +###### 2019-03-11 +* Add Heatmap Support +* Add Filterable Table + + +### caMicroscope [3.0.0](https://github.com/camicroscope/camicroscope/compare/v2.0.1...camicroscope:v3.0.0) +###### 2019-03-01 +* Completely redesigned UI/UX +* Redesigned Data Model +* Package/Application/Extension Infrastructure +* Testing Protocol + + +### caMicroscope [2.0.1](https://github.com/camicroscope/camicroscope/compare/v2.0...camicroscope:v2.0.1) +###### 2018-05-25 + + +### caMicroscope [2.0](https://github.com/camicroscope/camicroscope/compare/v1.0...camicroscope:v2.0) +###### 2018-05-18 + + +### caMicroscope [1.0](https://github.com/camicroscope/camicroscope/compare/v1.0...camicroscope:v1.0) +###### 2017-06-27 +* Tagging `release` branch as `1.0` per [@tkurc](https://github.com/tkurc) @ U24 mtg 7/27/2017 +* Dynamic services +* Better UI/colors for viewing annotations +* Disabled creation of annotations +* Viewing GEOJSON annotations in new UAIM3 format +* Creating Rectangles and FreeHand Polygons in UAIM3 format + -### Older Versions -see the [release log](https://github.com/camicroscope/caMicroscope/releases) +### Other Details +Refer to the [release log](https://github.com/camicroscope/caMicroscope/releases). diff --git a/README.md b/README.md index 95b6603a..c246d6b1 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ The toolbar is in the top-left of the main content window. Use the toolbar butto | ![](https://fonts.gstatic.com/s/i/materialicons/label/v4/24px.svg) | Labeling |Use this tool to draw a circle or rectangle around a tumor region, measure an area on the slide, download labels, and submit a bug report. The Labeling tool has its own toolbar with tools in the following order from left to right: return to the previous slide, place a square on the slide, place a circle on the slide, measure an area, download labels, and submit a bug report. Click the left arrow at the far right of the toolbar to hide it, then click the right arrow to show it. | | ![](https://fonts.gstatic.com/s/i/materialicons/timeline/v6/24px.svg) | Segment | This tool allows you to display, count, and export nuclear segmentations on the image. Clicking this tool opens the following custom toolbar. | | ![](https://fonts.gstatic.com/s/i/materialicons/aspect_ratio/v4/24px.svg) | Model | Show results from a pre-trained tensorflow compatible model on a ROI of the slide. | +| ![](https://fonts.gstatic.com/s/i/materialicons/get_app/v4/24px.svg) | Download Slide | Download the slide image to your system | | ![](https://fonts.gstatic.com/s/i/materialicons/bug_report/v4/24px.svg) | Bug Report | Report a bug or give feedback. | | ![](https://fonts.gstatic.com/s/i/materialicons/help/v4/24px.svg) | Tutorial | Click to view a guided tour of the viewer tools. | diff --git a/apps/signup/signup.html b/apps/signup/signup.html index 0f180645..9f1e1788 100644 --- a/apps/signup/signup.html +++ b/apps/signup/signup.html @@ -28,124 +28,14 @@ integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"> - - + - + @@ -218,7 +108,7 @@

User Signup

- Non-admin users have to submit a request ticket to admins to get their approval to signup new users. + Non-admin users have to submit a request ticket to admins to get their approval to signup new users. If you are an Admin, you can directly signup users.

@@ -227,7 +117,7 @@

User Signup

- + diff --git a/apps/signup/signup.js b/apps/signup/signup.js new file mode 100644 index 00000000..9936ee8b --- /dev/null +++ b/apps/signup/signup.js @@ -0,0 +1,108 @@ +var userSignupUrl = "../../data/User/post"; +var protoTokenUrl = "../../auth/Token/proto"; +var permissions; +const store = new Store('../data/'); + +function addUser(){ + var email = document.getElementById("mail").value + var filters = document.getElementById("filters").value + // var attr = document.querySelector('input[name="attr"]:checked').value + var attrEle = document.getElementById("attr"); + var attr = attrEle.options[attrEle.selectedIndex].value; + var userType = "Null" + if (attr == "3"){ + userType = "Admin" + } + if (attr == "2"){ + userType = "Editor" + } + + getUserPermissions(getUserType()) + .then(response => response.text()) + .then((data) => { + return (data ? JSON.parse(data) : null); + }) + .then((data)=> { + if(data===null) + return; + permissions = data; + return; + }) + .then((resp) => { + return fetch(protoTokenUrl) + }) + .then((response) => { + // console.log(response); + return response.json(); + }) + .then((data) => { + return data.exists + }) + .then((x) => { + if (x) { + var doc = {email: email, userType: userType, userFilter:filters} + fetch(userSignupUrl, { + method: 'POST', + mode: 'cors', // no-cors, cors, *same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'same-origin', // include, *same-origin, omit + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(doc), + }).then(x=>{ + if (x.status>=400){ + throw "failed to sign up user" + } + x.json() + }).then(x=>{ + window.alert("User registered successfully") + }).catch(e=>{ + // window.alert("error!") + console.error(e) + }); + } else { + if (permissions.user && permissions.user.post == true) { + var doc = {email: email, userType: userType, userFilter:filters} + fetch(userSignupUrl, { + method: 'POST', + mode: 'cors', // no-cors, cors, *same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'same-origin', // include, *same-origin, omit + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(doc), + }).then(x=>{ + if (x.status>=400){ + throw "failed to sign up user" + } + x.json() + }).then(x=>{ + window.alert("User registered successfully"); + }).catch(e=>{ + // window.alert("error!") + console.error(e) + }); + } else { + store.requestToCreateUser(email, filters, userType); + } + } + }); +} + +$(window).on('load', function() { + $('#sub').text('Sign up'); + $('#sub').removeAttr('disabled'); + + store.getUserPermissions(getUserType()) + .then(response => response.text()) + .then((data) => { + return (data ? JSON.parse(data) : null); + }) + .then((data)=> { + if(data===null) + return; + permissions = data; + }); +}); diff --git a/apps/table.html b/apps/table.html index b8a2d972..681c316e 100644 --- a/apps/table.html +++ b/apps/table.html @@ -24,90 +24,6 @@ - CaMicroscope Data Table diff --git a/apps/table.js b/apps/table.js new file mode 100644 index 00000000..85f1bcbe --- /dev/null +++ b/apps/table.js @@ -0,0 +1,886 @@ +function sanitize(string) { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + "/": '/', + }; + const reg = /[&<>"'/]/ig; + return string.replace(reg, (match)=>(map[match])); +} +var existingSlideNames = []; +var permissions; +const allowedExtensions = ['svs', 'tif', 'tiff', 'vms', 'vmu', 'ndpi', 'scn', 'mrxs', 'bif', 'svslide']; +function validateForm(callback) { + let slide = document.getElementById("slidename0"); + // Check if input element is rendered or not + if(slide===null) { + finishUploadSuccess = false; + $('#check_btn').hide(); + $('#post_btn').hide(); + changeStatus("UPLOAD", "Please chose a file first"); + return false; + } + //Check if slide name is empty + if (slide.value === "") { + finishUploadSuccess = false; + $('#check_btn').hide(); + $('#post_btn').hide(); + changeStatus("UPLOAD", "Please enter slide name"); + return false; + } + //Sanitizing input + slide.value = sanitize(slide.value); + //Checking if silde with given name already exists + if(existingSlideNames.includes(slide.value)) { + changeStatus("UPLOAD", "Slide with given name already exists"); + finishUploadSuccess = false; + $('#check_btn').hide(); + $('#post_btn').hide(); + return false; + } + //Checking for extension + let filename = document.getElementById("filename0").value + var fileExtension = filename.toLowerCase().split('.').reverse()[0]; + if(!allowedExtensions.includes(fileExtension)) { + finishUploadSuccess = false; + $('#check_btn').hide(); + $('#post_btn').hide(); + changeStatus("UPLOAD", fileExtension + " files are not compatible"); + return false; + } + let filterInput = $("#filter0"); + if(filterInput.val()) + { + try + { + let filters = filterInput.val().replace(/'/g, '"') + filters=JSON.parse(filters); + if(!Array.isArray(filters)) + throw new Error("Filters should be an array.") + else + { + filterInput.removeClass('is-invalid'); + if (filterInput.parent().children().length !== 1) { + $('#filter-feedback0').remove(); + } + } + } + catch(err) + { + filterInput.addClass('is-invalid'); + if (filterInput.parent().children().length === 1) { + filterInput.parent().append(`
+ Filters should be an array.
`); + } + return false; + } + } + callback(); +} + +// barrier -- REMOVE IF OK +let selectedFilters=[]; +const HeadMapping = [{ + title: 'ID', + field: 'oid' +}, { + title: 'Name', + field: 'name' +}, { + title: 'Study', + field: 'study' +}, { + title: 'Specimen', + field: 'specimen' +}, { + title: 'MPP', + field: 'mpp' +}]; +var totaltablepages; +var selectedpage; +var allSlides; + +if (getUserType() === "Admin") { + var slideDeleteRequests = []; + var userCreateRequests = []; +} + +function showTablePage(){ + $("#datatables tbody tr").filter(function(){ + $(this).hide(); + }); + + var trs = "#datatables tbody tr"; + $(trs).slice($("#entries").val()*selectedpage, $("#entries").val()*(selectedpage+1)).filter(function(){ + $(this).show(); + }); + + $(`.pages.active`).removeClass("active"); + $(`.pages:eq(${selectedpage})`).addClass("active"); +} + +function resetTable(){ + $('#datatables').stacktable(); + $(".pages").remove(); + $("#previous-page").after(function(){ + if(totaltablepages != 0) + return [...Array(totaltablepages).keys()].map((p)=>{ + return `
  • ${p+1}
  • `; + }).join(''); + }); + $(".pages").on('click', function(){ + selectedpage = parseInt($(this).text())-1; + showTablePage(); + }); + selectedpage = 0; + showTablePage(); +} + +function resetUploadForm() +{ + document.getElementById('upload-form').reset(); + $('#fileIdRow').children().slice(1).remove(); + $('#filenameRow').children().slice(1).remove(); + $('#tokenRow').children().slice(1).remove(); + $('#slidenameRow').children().slice(1).remove(); + $('#filterRow').children().slice(1).remove(); + $('#json_table').html(''); + $('#load_status').html(''); + $('.custom-file-label').html(''); + hideCheckButton(); + hidePostButton(); +} + +function createCheckbox(val) { + $("#filters-check").append(`
    + + +
    `); +} + +function initialize() { + let filters=getUserFilter(); + let isWildcard=false; + allSlides = []; + if(filters.length>1||(filters.length===1&&filters[0]!=="Public")) + { + selectedFilters = []; + $("#filters-heading").html('
    Filters
    ') + $("#filters-check").html(''); + let val = "Public"; + createCheckbox(val); + selectedFilters.push(val); + for (let i = 0; i < filters.length; i++) { + let val; + if (filters[i] == '**') { + isWildcard = true; + continue; + } + else + val = filters[i]; + selectedFilters.push(val); + createCheckbox(val); + } + if (isWildcard) { + val = 'Others'; + createCheckbox(val); + selectedFilters.push(val); + } + } + slideDeleteRequests = []; + userCreateRequests = []; + const params = getUrlVars(); + const store = new Store('../data/'); + store.findRequest() + .then(function (requests) { + // console.log(requests); + if (requests && ! requests.error) { + requests.forEach(function(req) { + if(req.type === "addUser") { + userCreateRequests.push(req); + } else { + slideDeleteRequests.push(req); + } + }) + } + store.findSlide() + .then(function (data) { + // filter + if (!(Object.keys(params).length === 0 && params.constructor === Object)) { + const keys = Object.keys(params); + const v2 = Object.values(params); + data = data.filter((d) => { + const v1 = getValues(d, keys); + return AND.call(this, v1, v2, eq); + }); + } + + // mapping data + const keys = HeadMapping.map(d => d.field); + if (data.map) { + return data.map((d, counter) => { + // console.log('i:' + counter); + const rs = []; + if(d['filter']) + { + rs.filterList= JSON.parse(d['filter'].replace(/'/g, '"')); + if(!rs.filterList.some((filter)=>(filters.indexOf(filter) > -1))) + rs.filterList=['Others']; + } + else + rs.filterList= ["Public"]; + rs.displayed=true; + const filename = d.location.split('/')[d.location.split('/').length - 1]; + keys.forEach((key, i) => { + if (i == 0) rs.push(d['_id']['$oid']) + else if (!d[key]) rs.push('') + else rs.push(d[key]) + }); + // console.log(d); + // if (getUserType() === "Admin") { + // if (d.deleteRequest) { + // slideDeleteRequests.push({ + // 'slideId': d._id.$oid, + // 'requestedBy': d.deleteRequest.requestedBy, + // 'slideName': d.deleteRequest.slideName, + // 'fileName': filename, + // }); + // } + // } + // console.log('reqId:' + slideDeleteRequests.find(o => o.slideDetails.slideId === rs[0])._id.$oid ); + // console.log(rs[0]); + // console.log(typeof(rs[0])); + // console.log(counter); + // console.log(slideDeleteRequests); + if (slideDeleteRequests['counter']) { + + console.log(slideDeleteRequests[counter]); + // console.log(slideDeleteRequests[counter - 1]); + } + // console.log('Done one iter'); + + const btn = `
    + + + ${ + slideDeleteRequests[counter] && slideDeleteRequests[counter].slideDetails && slideDeleteRequests.find(o => o.slideDetails.slideId === rs[0]) ? + ` + ${ + slideDeleteRequests.find(o => o.requestedBy === getUserId()) ? + ` + + ` : + ` + + ` + } + ` : + ` + + ` + } +
    ` + rs.push(btn); + return rs; + }); + } else { + // we have no data to render! Let's add a default button + const default_btn = []; + keys.forEach((key, i) => { + if (i == 0) default_btn.push("NO DATA") + else default_btn.push('') + }); + const btn = ``; + default_btn.push(btn); + return [default_btn]; + } + }) + .then(function (data) { + if(getUserType() === "Admin") { + appendNotifications(slideDeleteRequests); + } + return data; + }) + .then(function (data) { + if (data.length == 0) { + var div = document.querySelector('.container'); + div.textContent = `No Data Found ... x _ x`; + div.classList = `container text-center p-4`; + return; + } + + //Adding names to later validate for new slide names + existingSlideNames = data.map(d => d[1]); + + allSlides=data; + + const thead = HeadMapping.map((d,i) => `${d.title} `); + + thead.push(""); + tbody = data.map((d) => { + return "" + d.map((a) => "" + a + "").reduce((a, b) => a + b) + ""; + }); + let entriesPerPage; + if($('#entries').val()===undefined) + entriesPerPage=10; // default value, when initially no slide + else + entriesPerPage= $('#entries').val(); + totaltablepages = Math.ceil(data.length/entriesPerPage); + selectedpage = 0; + $("#search-table").val(""); + + if( data.length>0 && $('.container').children().length===0) + { + $('.container').html(`
    +
    +

    Available Slides

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +`); + } + document.getElementById("datatables").innerHTML = ` + ${thead.reduce((a, b) => a + b)} + ${tbody.reduce((a, b) => a + b)} + + + + `; + + showTablePage(); + + $("#search-table").on("keyup",filterSlides); + + $(".sort-btn").on("click", function(e) { + var index = e.currentTarget.dataset.index; + var order = parseInt(e.currentTarget.dataset.order); + const sortedSlideRows = allSlides.sort(function(a, b) { + let at=a[index]; + let bt=b[index]; + if(!isNaN(at)&&!isNaN(bt)) + { + at=Number(at); + bt=Number(bt); + } + else + { + at=at.toLowerCase(); + bt=bt.toLowerCase(); + } + if(order===1) + { + e.currentTarget.dataset.order = 2; + if(at>bt) + return 1; + else if(at bt) + return -1; + else + return 0; + } + }) + .filter(slide => slide.displayed) + .map((slide) => { + return "" + slide.map((a) => "" + a + "").reduce((a, b) => a + b) + ""; + }) + .reduce((a, b) => a + b, "") + $("#datatables > tbody").html(sortedSlideRows) + selectedpage = 0; + showTablePage(); + }); + + $(".pages").on('click', function(){ + selectedpage = parseInt($(this).text())-1; + showTablePage(); + }); + + $("#previous-page").on('click', function(){ + if(selectedpage > 0){ + selectedpage--; + showTablePage(); + } + }); + + $("#next-page").on('click', function(){ + if(selectedpage < totaltablepages-1){ + selectedpage++; + showTablePage(); + } + }) + + $("#entries").change(function(){ + totaltablepages = Math.ceil($("#datatables tbody tr").length/$("#entries").val()); + resetTable(); + pageIndicatorVisible($("#datatables tbody tr").length); + }); + pageIndicatorVisible($("#datatables tbody tr").length); + resetTable(); + $('#datatables').stacktable(); + checkUserPermissions(); + }); + }); +} + +function AND(p, t, func) { + return p.reduce((acc, v, i) => { + return acc && func.call(this, v, t[i]); + }, true); +} + +function eq(v1, v2) { + return v1 == v2; +} + +function getValues(d, keys) { + const rs = []; + keys.forEach(key => { + rs.push(d[key]); + }); + return rs; +} + +function openView(e) { + const oid = e.dataset.id; + if (oid) { + window.location.href = `./viewer/viewer.html?slideId=${oid}`; + } else { + alert('No Data Id'); + } +} + +function hidePostButton() +{ + $('#post_btn').hide(); +} + +function hideCheckButton() +{ + $('#check_btn').hide(); +} + +$('[data-dismiss=modal]').on('click', resetUploadForm); + +//window.addEventListener('resize', ()=>{$('#datatables').stacktable()}); +$(document).ready(function(){ + $("#slideUploadButton").hide(); + checkUserPermissions(); + initialize(); + $('#deleteModal').on('hidden.bs.modal', function (e) { + initialize(); + }); + $('#input').on('change',function(){ + var fileName = $(this).val().split('\\').pop(); + $(this).next('.custom-file-label').html(fileName); + }); + // Hide notification nav-link + if (getUserType() !== "Admin") { + $('#notifBell').html(''); + } +}); + +function checkUserPermissions(){ + let userType=getUserType(); + store.getUserPermissions(userType) + .then(response => response.text()) + .then((data) => { + return (data ? JSON.parse(data) : null); + }) + .then((data)=> { + if(data===null) + return; + permissions = data; + // console.log(data); + if(permissions.slide.post == true) + $("#slideUploadButton").show(); + if(permissions.slide.update == true){ + $("#datatables").find("tr").each(function(){ + var currentId = $("td:nth-child(1)", this).html(); + $("td:nth-child(2)", this).css("cursor","default"); + $("td:nth-child(2)", this).unbind('mouseenter mouseleave'); + $("td:nth-child(2)", this).hover(function(){ + var content = $(this).html(); + $(this).html(content +``) + }, function(){ + $( this ).find( "i" ).last().remove(); + }); + }); + } + }); +} + +function changeSlideName(oldname, id){ + $('#confirmUpdateSlideContent').html('Enter the new name for the slide having following details:

    id: '+id+'
    '+'Name: '+oldname + + '

    '); + const store = new Store('../data/'); + $('#confirmUpdateSlide').unbind('click'); + $('#confirmUpdateSlide').click(function(){ + var newSlideName = $("#newSlideName"); + var newName = newSlideName.val(); + if(newName!=''){ + if(existingSlideNames.includes(newName)) { + newSlideName.addClass('is-invalid'); + if (newSlideName.parent().children().length === 1) { + newSlideName.parent().append(`
    + Slide with given name already exists.
    `); + } + } + else { + newSlideName.removeClass('is-invalid'); + $('#slideNameChangeModal').modal('hide') + store.updateSlideName(id, newName).then((response)=>{ + return response.json(); + }).then((data)=>{ + if(data['modifiedCount']==1) + { + initialize(); + showSuccessPopup('Slide updated successfully'); + } + }); + } + } + }); +} + + +function pageIndicatorVisible(tableLength){ + if($("#entries").val() >= tableLength) + $("#tablePages").css("display","none"); + else if($("#entries").val() < tableLength) + $("#tablePages").css("display","block"); +} + +function urlUpload(){ + var url= document.getElementById("urlInput").value; + handleUrlUpload(url); +} +function downloadSlide(e){ + const oid = e.dataset.id; + handleDownload(oid); +} + +function deleteSld(e, cancel=false) { + + const userType = getUserType(); + const oid = e.dataset.id; + const oname = e.dataset.name; + const filename = e.dataset.filename; + const reqId = e.dataset.reqid; + // console.log('reqId ' + reqId); + + const store = new Store('../data/'); + if (oid) { + $('#confirmDeleteContent').html(`Are you sure you want to ${reqId ? 'decline the ': ''} ${permissions.slide.delete == true ? '' : 'request to ' } delete the slide ${oname} with id ${oid} ?`); + // $('#confirmDeleteContent').html(`Are you sure you want to ${reqId ? 'decline the ': ''} ${getUserType() === "Admin" ? '' : 'request to ' } delete the slide ${oname} with id ${oid} ?`); + $('#deleteModal').modal('toggle'); + $("#confirmDelete").unbind( "click" ); + $("#confirmDelete").click(function(){ + if (permissions.slide.delete == true && !cancel) { + // if (getUserType() === "Admin" && !cancel) { + deleteSlideFromSystem(oid, filename, reqId); // Delete slide + } else { + if (reqId) { + store.cancelRequestToDeleteSlide(requestId=reqId); // Cancel the delete request + } else { + store.requestToDeleteSlide(slideId=oid, slideName=oname, fileName=filename); // Add delete request + } + } + }); + } else { + alert('No Data Id'); + } + return true; +} + +function fileNameChange() +{ + hideCheckButton(); hidePostButton(); + const fileNameInput = $('#filename0'); + const fileName = fileNameInput.val(); + let newFileName = fileName.split(" ").join("_"); + fileNameInput.val(newFileName); + let fileExtension = newFileName.toLowerCase().split('.').reverse()[0]; + if (!allowedExtensions.includes(fileExtension)) { + fileNameInput.addClass('is-invalid'); + if(fileNameInput.parent().children().length===1) + { + fileNameInput.parent().append(`
    + ${fileExtension} files are not compatible
    `); + } + else + { + $('#filename-feedback0').html(`${ fileExtension } files are not compatible`) + } + } + else + { + fileNameInput.removeClass('is-invalid'); + if (fileNameInput.parent().children().length !== 1) { + $('#filename-feedback0').remove(); + } + } +} +function switchToFile(){ + $(".urlUploadClass").css("display","none"); + $(".fileInputClass").css("display","block"); + $("#urlswitch").css("display","block"); + $("#fileswitch").css("display","none"); + $('#uploadLoading').css('display', 'none'); +} + +function switchToUrl(){ + $(".urlUploadClass").css("display","flex"); + $(".fileInputClass").css("display","none"); + $("#urlswitch").css("display","none"); + $("#fileswitch").css("display","block"); +} + +function isValidHttpUrl(urlstring) { + try { + url = new URL(urlstring); + } catch (_) { + return false; + } + return url.protocol === "http:"; + } + +function urlInputChange() +{ + const urlInput = $('#urlInput'); + const url = urlInput.val(); + + if (!isValidHttpUrl(url)) { + urlInput.addClass('is-invalid'); + if (urlInput.parent().children().length === 2) { + urlInput.parent().append(`
    + Enter valid URL
    `); + } + + } + else + { + urlInput.removeClass('is-invalid'); + if (urlInput.parent().children().length !== 2) { + $('#inputUrl-feedback0').remove(); + } + urlUpload(); + } +} + + +function handleUserCreationRequests(e, cancel=false) { + const userType = getUserType(); + const reqId = e.dataset.reqid; + + if (cancel) { + store.cancelRequestToCreateUser(reqId); + } else { + const email = e.dataset.email; + const userType = e.dataset.usertype; + const userFilter = JSON.parse(e.dataset.filter.replace(/'/g, '"')); + store.cancelRequestToCreateUser(reqId, onlyRequestCancel=false).then(() => { + store.acceptRequestToDeleteSlide(email, userFilter, userType); + }) + } +} + + +function appendNotifications(slideDeleteRequests) { + $('#notification-nav-link').html(``); + $('#delReqTab').html(''); + $('#userReqTab').html(''); + $('#delReqBadge').html(''); + $('#userReqBadge').html(''); + if (slideDeleteRequests.length + userCreateRequests.length > 0) { + $('#notification-nav-link').html(`${slideDeleteRequests.length + userCreateRequests.length}`); + if (slideDeleteRequests.length > 0) { + $('#delReqBadge').html(`${slideDeleteRequests.length}`); + slideDeleteRequests.forEach((notif, i) => { + $('#delReqTab').append( + ` +
    +
    + +
    +
    + Slide Delete Requested +
    + Delete requested by:
    + User: ${notif.requestedBy}
    + Slide Name: ${notif.slideDetails.slideName} +
    +
    +
    +
    +
    +
    +
    +
    + ` + ); + }); + } else { + $('#delReqTab').append( + ` +
    +
    + No delete requests to show +
    +
    + ` + ); + } + if (userCreateRequests.length > 0) { + $('#userReqBadge').html(`${userCreateRequests.length}`); + userCreateRequests.forEach((notif, i) => { + console.log(notif); + $('#userReqTab').append( + ` +
    +
    + +
    +
    + User Registration Requested +
    + Requested by:
    + ${notif.requestedBy}
    + User details:
    + Email: ${notif.userDetails.email}
    + Filters: ${JSON.parse(notif.userDetails.userFilter.replace(/'/g, '"')).join(', ')}
    + Type: ${notif.userDetails.userType} +
    +
    +
    +
    +
    +
    +
    +
    + ` + ); + }); + } else { + $('#userReqTab').append( + ` +
    +
    + No user registration requests to show +
    +
    + ` + ); + } + } +} + +function handleFilterChange(target) +{ + let index= selectedFilters.indexOf(target.value); + if(target.checked && index < 0) + { + selectedFilters.push(target.value); + filterSlides(); + } + else + if(!target.checked && index >= 0) + { + selectedFilters.splice(index,1); + filterSlides(); + } +} + +function filterSlides() +{ +let value = String($("#search-table").val()).toLowerCase(); +let filters = getUserFilter(); +let filteredSlides; +if (filters.length > 1 || (filters.length === 1 && filters[0] !== "Public")) +{ +filteredSlides = allSlides.filter(function (slide) { + var slideFilters = slide.filterList; + let found = false; + for (let i = 0; i < selectedFilters.length; i++) { + if (slideFilters.indexOf(selectedFilters[i]) > -1) { + found = true; + break; + } + } + if(!found) + slide.displayed=false; + return found; +}); +} +else +filteredSlides=allSlides; +const searchedSlides = filteredSlides.filter(function (slide) { + var ind = slide.slice(0,5).reduce(function (a, b) { + return a +" "+ b; + }, " ").toLowerCase().indexOf(value); + if (ind > -1) { + slide.displayed=true; + return true; + } + else + { + slide.displayed=false; + return false; + } +}); +const newSlideRows = searchedSlides.map((d) => { + return "" + d.map((a) => "" + a + "").reduce((a, b) => a + b) + ""; +}); +$("#datatables tbody").html(newSlideRows.reduce((a, b) => a + b,"")) +totaltablepages = Math.ceil(newSlideRows.length / $("#entries").val()); +resetTable(); +pageIndicatorVisible(newSlideRows.length); +} diff --git a/apps/viewer/init.js b/apps/viewer/init.js index 4d6ce402..85543951 100644 --- a/apps/viewer/init.js +++ b/apps/viewer/init.js @@ -799,6 +799,15 @@ async function initUIcomponents() { }, }); + subToolsOpt.push({ + name: 'download', + icon: 'get_app', + title: 'Download Slide', + type: 'btn', + value: 'download slide', + callback: imageDownload, + }); + // -- For Nano borb Start -- // if (ImgloaderMode == 'imgbox') { // download diff --git a/apps/viewer/tut.js b/apps/viewer/tut.js index 2e0963db..82ade6c5 100644 --- a/apps/viewer/tut.js +++ b/apps/viewer/tut.js @@ -105,6 +105,14 @@ var tour = new Tour({ placement: 'auto bottom', smartPlacement: true, }, + { + element: 'i[title=\'Download Slide\']', + title: 'Download Slide', + content: + 'Download the slide image to your system', + placement: 'auto bottom', + smartPlacement: true, + }, { element: 'i[title=\'Bug Report\']', title: 'Bug Report', diff --git a/apps/viewer/uicallbacks.js b/apps/viewer/uicallbacks.js index 5a15849d..dc8080a6 100644 --- a/apps/viewer/uicallbacks.js +++ b/apps/viewer/uicallbacks.js @@ -672,10 +672,57 @@ function magnifierOff() { } // image download -function imageDownload(data) { - // TODO functionality - alert('Download Image'); - console.log(data); +function imageDownload() { + var downloadURL = '../../loader/getSlide/'; + var store = new Store('../../data/'); + var id=$D.params.slideId; + var fileName = ''; + store + .getSlide(id) + .then((response) => { + if (response[0]) { + return response[0]['location']; + } else { + throw new Error('Slide not found'); + } + }) + .then((location) => { + fileName = location.substring( + location.lastIndexOf('/') + 1, + location.length, + ); + return fileName; + }) + .then((fileName) => { + fetch(downloadURL + fileName, { + credentials: 'same-origin', + method: 'GET', + }) + .then((response) => { + if (response.status == 404) { + throw response; + } else { + return response.blob(); + } + }) + .then((blob) => { + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + a.remove(); // afterwards we remove the element again + window.URL.revokeObjectURL(blob); + }) + .catch((error) => { + console.log(error); + alert('Error! Can\'t download file.'); + }); + }) + .catch((error) => { + console.log(error); + }); } /** diff --git a/apps/viewer/viewer.html b/apps/viewer/viewer.html index 4932728f..793da642 100644 --- a/apps/viewer/viewer.html +++ b/apps/viewer/viewer.html @@ -324,6 +324,7 @@ + diff --git a/common/util.js b/common/util.js index 934ff329..ed553505 100644 --- a/common/util.js +++ b/common/util.js @@ -810,17 +810,6 @@ function getUserFilter() { return ['Public']; } } -function getUserPermissions(userType) { - const url = '/data/User/wcido'; - const query = { - 'ut': userType, - }; - return fetch(url + '?' + objToParamStr(query), { - method: 'GET', - credentials: 'include', - mode: 'cors', - }); -} function getUserId() { let token_info = parseJwt(getCookie('token')); diff --git a/core/Store.js b/core/Store.js index b068ecfc..919eefb1 100644 --- a/core/Store.js +++ b/core/Store.js @@ -72,6 +72,22 @@ class Store { } } } + /** + * Get a description of the system's user permissions + * @param {string} userType - the target user type. + **/ + getUserPermissions(userType) { + const suffix = 'User/wcido'; + const url = this.base + suffix; + const query = { + 'ut': userType, + }; + return fetch(url + '?' + objToParamStr(query), { + method: 'GET', + credentials: 'include', + mode: 'cors', + }); + } /** * find marks matching slide and/or marktype * will search by slide field as exactly given and by the oid slide of that name