From 05c3f21af490bb7087061364d33c92acf833bc2f Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 25 Oct 2023 15:24:35 -0400 Subject: [PATCH] mini viewer initial version --- apps/mini/init.js | 138 +---- apps/mini/layersViewer.js | 1068 +++++++++++++++++++++++++++++++++++++ apps/mini/uicallbacks.js | 283 +--------- apps/mini/viewer.html | 4 +- 4 files changed, 1081 insertions(+), 412 deletions(-) create mode 100644 apps/mini/layersViewer.js diff --git a/apps/mini/init.js b/apps/mini/init.js index da010fe3e..482327002 100644 --- a/apps/mini/init.js +++ b/apps/mini/init.js @@ -357,23 +357,6 @@ async function initUIcomponents() { callback: goHome, }); } - // pen - subToolsOpt.push({ - name: 'annotation', - icon: 'create', // material icons' name - title: 'Draw', - type: 'multistates', - callback: draw, - }); - - subToolsOpt.push({ - name: 'preset_label', - icon: 'colorize', // material icons' name - title: 'Preset Labels', - type: 'check', - value: 'prelabels', - callback: drawLabel, - }); // magnifier subToolsOpt.push({ @@ -423,15 +406,6 @@ async function initUIcomponents() { callback: toggleMeasurement, }); } - // donwload selection - subToolsOpt.push({ - name: 'download_selection', - icon: 'get_app', // material icons' name - title: 'Download Selection', - type: 'check', - value: 'download', - callback: toggleDownloadSelection, - }); // enhance subToolsOpt.push({ name: 'Enhance', @@ -465,112 +439,21 @@ async function initUIcomponents() { callback: enhance, }); // share - if (ImgloaderMode == 'iip') { - subToolsOpt.push({ - name: 'share', - icon: 'share', - title: 'Share View', - type: 'btn', - value: 'share', - callback: shareURL, - }); - } - // side-by-side - subToolsOpt.push({ - name: 'sbsviewer', - icon: 'view_carousel', - title: 'Side By Side Viewer', - value: 'dbviewers', - type: 'check', - callback: toggleViewerMode, - }); - // heatmap - subToolsOpt.push({ - name: 'heatmap', - icon: 'satellite', - title: 'Heat Map', - value: 'heatmap', - type: 'btn', - callback: openHeatmap, - }); - subToolsOpt.push({ - name: 'labeling', - icon: 'label', - title: 'Labeling', - value: 'labeling', - type: 'btn', - callback: function() { - window.location.href = `../labeling/labeling.html${window.location.search}`; - }, - }); - subToolsOpt.push({ - name: 'segment', - icon: 'timeline', - type: 'btn', - value: 'rect', - title: 'Segment', - callback: function() { - if (window.location.search.length > 0) { - window.location.href = - '../segment/segment.html' + window.location.search; - } else { - window.location.href = '../segment/segment.html'; - } - }, - }); + subToolsOpt.push({ - name: 'model', - icon: 'aspect_ratio', + name: 'share', + icon: 'share', + title: 'Share View', type: 'btn', - value: 'rect', - title: 'Predict', - callback: function() { - if (window.location.search.length > 0) { - window.location.href = '../model/model.html' + window.location.search; - } else { - window.location.href = '../model/model.html'; - } - }, + value: 'share', + callback: shareURL, }); - // -- For Nano borb Start -- // - if (ImgloaderMode == 'imgbox') { - // download - subToolsOpt.push({ - name: 'downloadmarks', - icon: 'cloud_download', - title: 'Download Marks', - type: 'btn', - value: 'download', - callback: Store.prototype.DownloadMarksToFile, - }); - subToolsOpt.push({ - name: 'uploadmarks', - icon: 'cloud_upload', - title: 'Load Marks', - type: 'btn', - value: 'upload', - callback: Store.prototype.LoadMarksFromFile, - }); - } - // -- For Nano borb End -- // - - // -- view btn START -- // - if (!($D.params.data.hasOwnProperty('review') && $D.params.data['review']=='true')) { - subToolsOpt.push({ - name: 'review', - icon: 'playlist_add_check', - title: 'has reviewed', - type: 'btn', - value: 'review', - callback: updateSlideView, - }); - } // screenshot subToolsOpt.push({ name: 'slideCapture', icon: 'camera_enhance', - title: 'Slide Capture', + title: 'Download View as Image', type: 'btn', value: 'slCap', callback: captureSlide, @@ -658,13 +541,6 @@ async function initUIcomponents() { // text:'notes', // callback:annoEdit // }, - { - // delete - title: 'Delete', - class: 'material-icons', - text: 'delete_forever', - callback: annoDelete, - }, ], }); diff --git a/apps/mini/layersViewer.js b/apps/mini/layersViewer.js new file mode 100644 index 000000000..5d94b57a3 --- /dev/null +++ b/apps/mini/layersViewer.js @@ -0,0 +1,1068 @@ +// proposal: +// test: +// constructor +// setData +// callback +// style: +// expand/collapse if click on a node +// search bar is workeds + +/** + * CaMicroscope Layers Viewer. A component that shows all layers by the different categories. + * @constructor + * @param {Object} options + * All required and optional settings for instantiating a new instance of a Layer Manager. + * @param {String} options.id + * Id of the element to append the Layer Manager's container element to. + * @param {Object[]} options.data + * the data set of the layers. + * @param {String} options.data.id + * layer's id + * @param {String} options.data.name + * layer's name + * @param {String} options.data.typeId + * layer's type id + * @param {String} options.data.typeName + * layer's type name + * + */ +function LayersViewer(options) { + this.className = 'LayersViewer'; + this.setting = { + // id: doc element + // data: layers dataset + // categoricalData + isSortableView: false, + }; + this.defaultType = ['human', 'ruler', 'segmentation', 'heatmap']; + + /** + * @property {Object} _v_model + * View Model for the layers manager + * @property {Object} _v_model.types + * data set group by types + * @property {String} _v_model.types.name + * type name + * @property {Array} _v_model.types.data[] + * what layers in this type of layers + */ + + this.viewRadios; // name: + this.searchBar; // input and btn + + this.categoricalView; + // this.sortableView; + // this.sortable; + + // setting dataset + extend(this.setting, options); + this.elt = document.getElementById(this.setting.id); + if (!this.elt) { + console.error(`${this.className}: No Main Elements...`); + return; + } + this.elt.classList.add('layer_viewer'); + + // sort data + // this.setting.data.sort(LayersViewer.compare); + // give index + // convert og data to categorical data + this.setting.categoricalData = { + heatmap: { + item: {id: 'heatmap', name: 'heatmap'}, + items: [], + }, + segmentation: { + item: {id: 'segmentation', name: 'segmentation'}, + items: [], + }, + ruler: { + item: {id: 'ruler', name: 'ruler'}, + items: [], + }, + human: { + item: {id: 'human', name: 'human'}, + items: [], + }, + }; + // this.__covertData(); + this.__initUI(); + } + + LayersViewer.prototype.toggleAllItems = function( + isShow = false, + fresh = true, + ) { + this.setting.data.forEach((d) => (d.isShow = isShow)); + if (fresh) this.update(); + }; + + LayersViewer.prototype.addHumanItem = function( + item, + type, + parent, + isShow = true, + ) { + if (!this.defaultType.includes(type)) { + console.warn('Error Type !!!'); + return; + } + + var cate = this.setting.categoricalData[type].items[parent]; + if (!cate) { + // no parent node + const newCate = {}; + if (item.label) { + newCate[item.label.id] = { + item: { + id: item.label.id, + name: item.label.name, + }, + items: [], + }; + this.setting.categoricalData[type].items[parent] = newCate; + this.addHumanItems(newCate); + cate = this.setting.categoricalData[type].items[parent]; + } else { + console.error('Layersviewer.addHumanItem has error'); + } + } + + const data = {item, isShow}; + // add Data + cate.items.push(data); + + // add item on UI + data.elt = document.createElement('li'); + data.elt.dataset.id = data.item.id; + data.elt.dataset.title = data.item.label ? + `${data.item.name}${data.item.id}` : + `${data.item.name}`; + data.elt.innerHTML = `
room
+ + `; + + // event: show/hidden layers for each annotation + const chk = data.elt.querySelector('input[type=checkbox][data-type=leaf]'); + chk.addEventListener('change', this.__change.bind(this)); + // + const removeDiv = data.elt.querySelector('div.material-icons.remove'); + removeDiv.addEventListener('click', () => { + this.setting.removeCallback.call(this, data, cate.item.id); + }); + const locationDiv = data.elt.querySelector('div.material-icons.location'); + locationDiv.addEventListener('click', () => { + this.setting.locationCallback.call(this, data); + }); + + cate.children.insertBefore(data.elt, cate.children.firstChild); + // update num + cate.num.textContent = cate.items.length; + cate.elt.style.display = 'flex'; + + // total human anotation nums + var humanNum = 0; + const obj = this.setting.categoricalData[type].items; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + humanNum += obj[key].items.length; + } + } + this.setting.categoricalData[type].num.textContent = humanNum; + }; + + LayersViewer.prototype.addItem = function( + item, + type, + isShow = true, + fresh = true, + ) { + if (!this.defaultType.includes(type)) { + console.warn('Error Type !!!'); + return; + } + const cate = this.setting.categoricalData[type]; + const items = cate.items; + const data = {item, isShow}; + // add Data + items.push(data); + + // add item on UI + data.elt = LayersViewer.createCategoricalItem.call(this, data); + const chk = data.elt.querySelector('input[type=checkbox]'); + // add show/hidden event on check box + chk.addEventListener('change', this.__change.bind(this)), + // cate.children.appendChild(data.elt) + cate.children.insertBefore(data.elt, cate.children.firstChild); + + // update num + cate.num.textContent = cate.items.length; + }; + + LayersViewer.prototype.removeItemById = function( + id, + type, + parent, + fresh = true, + ) { + if (!this.defaultType.includes(type)) { + console.warn('Error Type !!!'); + return; + } + + const cate = + type == 'human' ? + this.setting.categoricalData[type].items[parent] : + this.setting.categoricalData[type]; + const items = cate.items; + const index = items.findIndex((d) => d.item.id == id); + if (index == -1) return; + const li = items[index].elt; + // ui remove + li.parentNode.removeChild(li); + // data remove + items.splice(index, 1); + // change num + cate.num.textContent = items.length; + if (type == 'human') { + if (cate.items.length == 0) cate.elt.style.display = 'none'; + var humanNum = 0; + const obj = this.setting.categoricalData[type].items; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + humanNum += obj[key].items.length; + } + } + this.setting.categoricalData[type].num.textContent = humanNum; + } + }; + + LayersViewer.prototype.getDataItemById = function(id, type, parent) { + if (!this.defaultType.includes(type)) { + console.warn('Error Type !!!'); + return; + } + + const cate = + type == 'human' ? + this.setting.categoricalData[type].items[parent] : + this.setting.categoricalData[type]; + const items = cate.items; + return items.find((d) => d.item.id == id); + }; + + LayersViewer.prototype.onEnd = function(e) { + if (e.newIndex === e.oldIndex) return; + const data = this.setting.data; + + // move data from old index position to new index position + LayersViewer.moveArray(data, e.oldIndex, e.newIndex); + // refresh UI + const sort = data.map((item) => item.id); + this.sortable.sort(sort); + // callback functions + if (this.setting.sortChange) this.setting.sortChange.call(null, sort); + }; + LayersViewer.prototype.moveLayer = function(id, direction = 'up') { + const data = this.setting.data; + // find layer index + const oldIndex = data.findIndex((item) => item.id === id); + + const newIndex = direction === 'up' ? oldIndex - 1 : oldIndex + 1; + + if (newIndex < 0 || newIndex >= data.length) { + console.warn('move: Out Of Index'); + return; + } + // move data from old index position to new index position + LayersViewer.moveArray(data, oldIndex, newIndex); + + // refresh UI + const sort = data.map((item) => item.id); + this.sortable.sort(sort); + // callback function + if (this.setting.sortChange) this.setting.sortChange.call(null, sort); + }; + + // move + LayersViewer.moveArray = function(array, oldIndex, newIndex) { + if (newIndex >= array.length) { + let dist = newIndex - array.length + 1; + while (dist--) { + array.push(undefined); + } + } + array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]); + return array; // for testing + }; + + // for sorting + LayersViewer.compare = function(a, b) { + if (a.index && b.index) return a.index > b.index; + if (a.index) return 1; + if (b.index) return 1; + return a.name > b.name; + }; + + /** + * [__clearUI description] + * @return {[type]} [description] + */ + LayersViewer.prototype.__clearUI = function() { + empty(this.elt); + this.searchBar = null; + this.viewRadios = null; + this.categoricalView = null; + this.sortableView = null; + this.sortable = null; + }; + /* + refresh UI + */ + LayersViewer.prototype.__initUI = function() { + empty(this.elt); // clear all elements + this.__clearUI(); + + /* create search bar area START */ + const ctrlObj = LayersViewer.createControlBar(); + // this.viewRadios = ctrlObj.viewRadios; // view switch + this.searchBar = ctrlObj.searchBar; // searchbar + this.elt.appendChild(ctrlObj.view); + + /* create search bar area END */ + const checkDiv = document.createElement('div'); + checkDiv.classList.add('checklist'); + checkDiv.style.display = 'none'; + checkDiv.innerHTML = ` + + `; + this.elt.appendChild(checkDiv); + this.searchList = checkDiv; + [...this.searchList.querySelectorAll('input[type=checkbox]')].forEach((chk) => + chk.addEventListener('change', (e) => { + this.__search(this.searchBar.text.value); + }), + ); + /* categorical view START*/ + // create categorical view div + const cateViewDiv = document.createElement('div'); + cateViewDiv.classList.add('layers_list'); + + // create categorical view content + const objCategories = LayersViewer.createCategoricalView.call( + this, + this.setting.categoricalData, + ); + cateViewDiv.appendChild(objCategories.view); + + this.elt.appendChild(cateViewDiv); + this.categoricalView = cateViewDiv; + /* categorical view END*/ + + /* sortable view START */ + // create sortable view div + // let sort_view_div = document.createElement('div'); + // sort_view_div.classList.add('sort_list'); + // sort_view_div.style.display = 'none'; + + // create sortable view content + // const objSortable = LayersViewer.createSortableView(this.setting.data); + // this.sortable = objSortable.sortable; + // this.sortable.option('onEnd', this.onEnd.bind(this)) + + // add the content into the div + // sort_view_div.appendChild(objSortable.view); + // this.elt.appendChild(sort_view_div); + // this.sortableView = sort_view_div; + /* sortable view END */ + + // add search event + this.searchBar.btn.addEventListener('click', this.__search.bind(this)); + // + this.searchBar.text.addEventListener('change', this.__search.bind(this)); + this.searchBar.text.addEventListener('keyup', this.__search.bind(this)); + + // add event for all checkbox + const checkboxes = this.elt.querySelectorAll('input[type=checkbox]'); + checkboxes.forEach((chk) => + chk.addEventListener('change', this.__change.bind(this)), + ); + // expand and collapse control for categorical view + const cateItems = this.setting.categoricalData; + for (const key in cateItems) { + if (cateItems.hasOwnProperty(key)) { + cateItems[key].elt.firstChild.addEventListener( + 'click', + this.__switch.bind(this), + ); + } + } + + // moveup + // const ups = this.sortableView.querySelectorAll('.moveup'); + // ups.forEach( up => up.addEventListener('click',function(e){ + // const _id = e.target.parentNode.dataset.id; + // this.moveLayer(_id,'up'); + // }.bind(this))); + + // movedown + // const downs = this.sortableView.querySelectorAll('.movedown'); + // downs.forEach( downs => downs.addEventListener('click',function(e){ + // const _id = e.target.parentNode.dataset.id; + // this.moveLayer(_id,'down'); + // }.bind(this))); + + // initialize checkbox + // if(this.setting.isSortableView){ + // this.viewModeSwitch('sort'); + // }else{ + // this.viewModeSwitch('category'); + // } + }; + + // click on view radio btn which changes the view mode + LayersViewer.prototype.__radioClick = function() { + const mode = this.elt.querySelector( + `input[type=radio][name=${this.viewRadios.name}]:checked`, + ).value; + this.viewModeSwitch(mode); + if (mode === 'sort') { + this.setting.isSortableView = true; + } else { + this.setting.isSortableView = false; + } + }; + + // switch layers view mode - sortable or categorical + LayersViewer.prototype.viewModeSwitch = function( + mode = 'category', /* category/sort */ + ) { + // display two views + this.sortableView.style.display = 'none'; + this.categoricalView.style.display = 'none'; + + switch (mode) { + case 'category': + // open category view and checked cate radio btn + this.viewRadios.list[0].elt.checked = true; + this.categoricalView.style.display = 'block'; + break; + case 'sort': + // open sortable view and checked sort radio btn + this.viewRadios.list[1].elt.checked = true; + this.sortableView.style.display = 'block'; + break; + default: + // statements_def + break; + } + }; + + /* For Categorical View functions START */ + LayersViewer.createCategoricalView = function(data) { + const ul = document.createElement('ul'); + for (const typeId in data) { + if (data.hasOwnProperty(typeId)) { + // create root li + const typeData = data[typeId]; // typeData = {id:typeId,name:typeName,items:[{item:,isShow:}]} + typeData.elt = LayersViewer.createCategoricalItem.call( + this, + typeData, + 'root', + ); + typeData.num = typeData.elt.querySelector('div.num'); + ul.appendChild(typeData.elt); // create li root + + const children = document.createElement('ul'); + children.style.display = 'none'; + // add leaf + // typeData.items.forEach( + // function(item) { + // item.elt = LayersViewer.createCategoricalItem.call(this, item); + // children.appendChild(item.elt); // create li leaf + // }.bind(this), + // ); + // + ul.appendChild(children); + typeData.children = children; + } + } + return {view: ul}; + }; + + LayersViewer.prototype.addHumanItems = function(data) { + const human = this.setting.categoricalData['human']; + // human.items = data; + + const ul = document.createElement('ul'); + var num = 0; + for (const [id, cate] of Object.entries(data)) { + human.items[id] = cate; + const name = cate.item.name; + const li = document.createElement('li'); + li.dataset.id = id; + li.style.display = cate.items.length ? null : 'none'; + num += cate.items.length; + // create + li.innerHTML = `
keyboard_arrow_right
+ + `; + + const allChk = li.querySelector('input[type=checkbox][data-type=root]'); + allChk.addEventListener('change', this.__change.bind(this)); + const children = document.createElement('ul'); + children.style.display = 'none'; + + cate.items.forEach((data) => { + data.elt = document.createElement('li'); + data.elt.dataset.id = data.item.id; + data.elt.dataset.title = data.item.label ? + `${data.item.name}${data.item.id}` : + `${data.item.name}`; + data.elt.innerHTML = ` + + `; + children.appendChild(data.elt); + + // event: show/hidden layers for each annotation + const chk = data.elt.querySelector( + 'input[type=checkbox][data-type=leaf]', + ); + chk.addEventListener('change', this.__change.bind(this)); + // + const locationDiv = data.elt.querySelector('div.material-icons.location'); + locationDiv.addEventListener('click', () => { + this.setting.locationCallback.call(this, data); + }); + }); + + cate.elt = li; + cate.num = li.querySelector('div.num'); + cate.children = children; + human.children.appendChild(li); + human.children.appendChild(cate.children); + + // event for cate + + // add change on reaf checkbox + // expand/collapse + cate.elt.firstChild.addEventListener('click', this.__switch.bind(this)); + // + } + + // show up arrow icon + const arrowIcon = human.elt.querySelector('div.material-icons'); + arrowIcon.style.display = null; + // remove loading icon + const loadingIcon = human.elt.querySelector('div.loading-icon'); + // const chk = human.elt.querySelector("input[type=checkbox]"); + // chk.style.display = ""; + human.num.textContent = num; + if (loadingIcon) loadingIcon.parentNode.removeChild(loadingIcon); + }; + + LayersViewer.prototype.addItems = function(data, type) { + const typeData = this.setting.categoricalData[type]; + + // show up arrow icon + const arrowIcon = typeData.elt.querySelector('div.material-icons'); + arrowIcon.style.display = null; + // remove loading icon + const loadingIcon = typeData.elt.querySelector('div.loading-icon'); + if (loadingIcon) loadingIcon.parentNode.removeChild(loadingIcon); + + if (type == 'human' || type == 'ruler') { + this.toggleSearchPanel(); + const chk = typeData.elt.querySelector('input[type=checkbox]'); + chk.style.display = ''; + } + + // create ui item + data.forEach((item) => { + item.elt = LayersViewer.createCategoricalItem.call(this, item); + const chk = item.elt.querySelector('input[type=checkbox]'); + // add show/hidden event on check box + chk.addEventListener('change', this.__change.bind(this)), + typeData.children.appendChild(item.elt); + }); + + typeData.items = [...typeData.items, ...data]; + typeData.num.textContent = typeData.items.length; + }; + + LayersViewer.createCategoricalItem = function(data, type) { + const item = data.item; + const id = `cate.${item.id}`; + // create li + const li = document.createElement('li'); + li.dataset.id = item.id; + // data? + + // label + const label = document.createElement('label'); + label.htmlFor = id; + + // div + const text = document.createElement('div'); + text.textContent = type == 'root' ? titleCase(item.name) : item.name; + + label.appendChild(text); + + // checkbox + const chk = document.createElement('input'); + chk.type = 'checkbox'; + + chk.dataset.id = item.id; + + if (type === 'root') { + const ic = document.createElement('div'); + ic.classList.add('material-icons'); + ic.textContent = 'keyboard_arrow_right'; + ic.style.display = 'none'; + label.style.fontWeight = 'bold'; + chk.dataset.type = 'root'; + chk.style.display = 'none'; + li.appendChild(ic); + const loadingIcon = document.createElement('div'); + loadingIcon.classList.add('loading-icon'); + label.prepend(loadingIcon); + const num = document.createElement('div'); + num.classList.add('num'); + label.append(num); + } else { + chk.id = id; + chk.dataset.cate = item.typeId; + chk.dataset.type = 'leaf'; + chk.checked = data.isShow; + li.title = item.name; + } + + if (item.typeName && (item.typeName == 'human' || item.typeName == 'ruler')) { + // remove and relocation + const removeDiv = document.createElement('div'); + removeDiv.classList.add('material-icons'); + removeDiv.classList.add('md-24'); + removeDiv.textContent = 'clear'; + removeDiv.title = 'Remove'; + const locationDiv = document.createElement('div'); + locationDiv.style.display = data.isShow ? 'block' : 'none'; + // bind event location_searching + locationDiv.classList.add('material-icons'); + locationDiv.classList.add('md-24'); + locationDiv.classList.add('location'); + locationDiv.textContent = 'room'; + locationDiv.title = 'Location'; + + // + removeDiv.addEventListener('click', () => { + this.setting.removeCallback.call(this, data); + }); + locationDiv.addEventListener('click', () => { + this.setting.locationCallback.call(this, data); + }); + + // + li.appendChild(locationDiv); + li.appendChild(label); + li.appendChild(removeDiv); + } else { + li.appendChild(label); + } + + // chk.dataset.name = item.name; + + li.appendChild(chk); + return li; + }; + /* For Categorical View functions END */ + + /* For Sortable View functions START */ + LayersViewer.createSortableView = function(list) { + const sortableList = document.createElement('ul'); + for (let i = 0; i < list.length; i++) { + const item = list[i]; + const elt = LayersViewer.createSortableItem(item); + item.sortItem = elt; + sortableList.appendChild(elt); + } + + // sortable options + const options = { + group: 'share', + animation: 100, + dataIdAttr: 'data-id', + }; + + // + const sortable = Sortable.create(sortableList, options); + return {sortable: sortable, view: sortableList}; + }; + + // create sortable item for sortable view + LayersViewer.createSortableItem = function(item) { + const id = `sort.${item.id}`; + + // create li + const li = document.createElement('li'); + li.dataset.id = item.id; + // data? + + // label + const label = document.createElement('label'); + label.htmlFor = id; + + // div + const text = document.createElement('div'); + text.textContent = item.name; + label.appendChild(text); + + // div moveup + const upIcon = document.createElement('div'); + upIcon.classList.add('material-icons'); + upIcon.classList.add('moveup'); + upIcon.textContent = 'arrow_drop_up'; + + // div movedown + const downIcon = document.createElement('div'); + downIcon.classList.add('material-icons'); + downIcon.classList.add('movedown'); + downIcon.textContent = 'arrow_drop_down'; + + // checkbox + const chk = document.createElement('input'); + chk.type = 'checkbox'; + chk.id = id; + chk.checked = item.isShow; + chk.dataset.id = item.id; + chk.dataset.type = 'leaf'; + li.appendChild(label); + li.appendChild(upIcon); + li.appendChild(downIcon); + li.appendChild(chk); + return li; + }; + /* For Sortable View functions END */ + LayersViewer.prototype.toggleSearchPanel = function(isShow = true) { + if (isShow) { + this.searchBar.elt.style.display = null; + this.searchList.style.display = null; + } else { + this.searchBar.elt.style.display = 'none'; + this.searchList.style.display = 'none'; + } + }; + /* For control area functions START */ + LayersViewer.createControlBar = function() { + const view = document.createElement('div'); + view.style.display = 'none'; + view.classList.add('searchbar'); + + // create view radios name + const _name = randomId(); + + /* create cate btn START */ + const cateId = randomId(); + const cateBtn = document.createElement('div'); + cateBtn.style.display = 'none'; + // cate radio + const cateRadio = document.createElement('input'); + cateRadio.id = cateId; + cateRadio.type = 'radio'; + cateRadio.name = _name; + cateRadio.value = 'category'; + + // cate label + const cateLabel = document.createElement('label'); + cateLabel.htmlFor = cateId; + cateLabel.classList.add('material-icons'); + cateLabel.textContent = 'category'; + + cateBtn.appendChild(cateRadio); + cateBtn.appendChild(cateLabel); + + // add into view + view.appendChild(cateBtn); + /* create cate btn END */ + + /* create cate btn START */ + const sortId = randomId(); + const sortBtn = document.createElement('div'); + sortBtn.style.display = 'none'; + const sortRadio = document.createElement('input'); + sortRadio.id = sortId; + sortRadio.type = 'radio'; + sortRadio.name = _name; + sortRadio.value = 'sort'; + + const sortLabel = document.createElement('label'); + sortLabel.htmlFor = sortId; + sortLabel.classList.add('material-icons'); + sortLabel.textContent = 'sort'; + + sortBtn.appendChild(sortRadio); + sortBtn.appendChild(sortLabel); + + // add into view + view.appendChild(sortBtn); + /* create sort btn END */ + + /* create search bar START */ + // text input + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.placeholder = 'Search By Name/Creator'; + + // search btn + const searchBtn = document.createElement('div'); + searchBtn.classList.add('material-icons'); + searchBtn.textContent = 'find_in_page'; + + // add input and btn into ciew + view.appendChild(searchInput); + view.appendChild(searchBtn); + /* create search bar END */ + + /* create check all START */ + const chkAll = document.createElement('input'); + chkAll.style.display = 'none'; + chkAll.type = 'checkbox'; + chkAll.id = 'all'; + chkAll.dataset.type = 'all'; + view.appendChild(chkAll); + /* create check all END */ + + // return obj for switch layer views + const viewRadios = {}; + viewRadios.name = _name; + viewRadios.list = [ + { + id: cateId, + elt: cateRadio, // categorical view radio btn + }, + { + id: sortId, + elt: sortRadio, // sortable view radio btn + }, + ]; + + // return obj for search bar + const searchBar = {}; + searchBar.elt = view; + searchBar.text = searchInput; + searchBar.btn = searchBtn; + + // return obj for check all + + return { + view: view, + viewRadios: viewRadios, // view switch + searchBar: searchBar, // searchbar + checkAll: chkAll, // check all + }; + }; + + /* For control area functions END */ + /** + * Set data model and automatically refresh UI. + * @param {Object[]} data + * the data set of the layers. + * @param {String} data.id + * layer's id + * @param {String} data.name + * layer's name + * @param {String} data.typeId + * layer's type id + * @param {String} data.typeName + * layer's type name + * + */ + LayersViewer.prototype.setData = function(data) { + this.setting.data = data; + this.update(); + }; + /** + * @private + * update the UI according to the data model + */ + LayersViewer.prototype.update = function() { + // this.setting.data = data; + // this.__covertData(); + this.__refreshUI(); + }; + + /** + * @private + * search event + * @param {[type]} e [description] + */ + LayersViewer.prototype.__search = function(e) { + // show all li with leaf class + const human = []; + for (const [key, data] of Object.entries( + this.setting.categoricalData['human'].items, + )) { + human.push(...data.items); + } + + const ruler = this.setting.categoricalData['ruler'].items; + const heatmap = this.setting.categoricalData['heatmap'].items; + const segmentation = this.setting.categoricalData['segmentation'].items; + const list = [...human, ...ruler, ...heatmap, ...segmentation]; + + list.forEach((data) => { + data.elt.style.display = 'flex'; + }); + + const pattern = this.searchBar.text.value; + + const regex = new RegExp(pattern, 'gi'); + const checkedType = [ + ...this.searchList.querySelectorAll('input:checked'), + ].map((elt) => elt.value); + list + .filter((d) => d.item.typeName == 'human' || d.item.typeName == 'ruler') + .forEach((data) => { + if ( + !data.item.name.match(regex) && + !(data.item.creator && data.item.creator.match(regex)) + ) { + data.elt.style.display = 'none'; + } + if ( + data.item.typeName == 'human' && + !checkedType.includes(data.item.shape) + ) { + data.elt.style.display = 'none'; + } + }); + }; + + /* + convert og/raw data to categorical model/data + */ + // LayersViewer.prototype.__covertData = function() { + // if (!this.setting.data) { + // console.warn(`${this.className}: No Raw Data`); + // // return; + // } + // this.setting.categoricalData = this.setting.data.reduce(function(model, d) { + // const item = d.item; + // if (!model[item.typeId]) { + // model[item.typeId] = { + // item: {id: item.typeId, name: item.typeName}, + // items: [], + // }; + // } + // model[item.typeId].items.push(d); + // return model; + // }, {}); + // }; + + // expand or collapse layer list + LayersViewer.prototype.__switch = function(e) { + const nextElt = e.target.parentNode.nextElementSibling; + if (nextElt.style.display == 'none') { + nextElt.style.display = null; + e.target.innerHTML = 'keyboard_arrow_down'; + } else { + nextElt.style.display = 'none'; + e.target.innerHTML = 'keyboard_arrow_right'; + } + }; + + // + LayersViewer.prototype.__change = function(e) { + // confirm TODO + const dataset = e.target.dataset; + const id = dataset.id; + const type = dataset.type; + const name = dataset.name; + const checked = e.target.checked; + + switch (type) { + case 'root': + var items; + var root; + if (dataset.root == 'human') { + root = dataset.root; + items = this.setting.categoricalData['human'].items[dataset.id].items; + } else { + root = id; + items = this.setting.categoricalData[id].items; + } + + items.forEach((d) => { + if (d.elt.style.display == 'none') return; + d.isShow = checked; + d.elt.lastChild.checked = checked; + if (checked) { + d.elt.firstChild.style.display = ''; + } else { + d.elt.firstChild.style.display = 'none'; + } + }); + this.setting.rootCallback.call(null, { + root, + parent: id, + parentName: name, + items, + }); + break; + case 'leaf': + var data; + if (dataset.parent && dataset.root == 'human') { + // human annotation + data = this.setting.categoricalData['human'].items[ + dataset.parent + ].items.find((d) => d.item.id == id); + } else { + const cate = dataset.cate; + data = this.setting.categoricalData[cate].items.find( + (d) => d.item.id == id, + ); + } + if (!data) return; + data.isShow = checked; + data.elt.lastChild.checked = checked; + + const location = e.target.parentElement.querySelector('div.location'); + if (location && checked) { + location.style.display = 'block'; + } else if (location && !checked) { + location.style.display = 'none'; + } + + this.setting.callback.call(null, [data]); + break; + default: + } + }; + + // TODO + LayersViewer.prototype.add = function() {}; + // TODO + LayersViewer.prototype.remove = function() {}; + \ No newline at end of file diff --git a/apps/mini/uicallbacks.js b/apps/mini/uicallbacks.js index aea890805..1fbfd496b 100644 --- a/apps/mini/uicallbacks.js +++ b/apps/mini/uicallbacks.js @@ -684,54 +684,7 @@ function convertHumanAnnotationToPopupBody(notes) { * @param {Object} data */ function annoDelete(data, parentType) { - if (!data.id) return; - - - const annotationData = $D.humanlayers.find( - (d) => d.data && d.data._id.$oid == data.oid, - ); - let message; - if (annotationData.data.geometries) { - message = `Are You Sure You Want To Delete This Annotation {ID:${data.id}} With ${annotationData.data.geometries.features.length} Mark(s)?`; - } else { - message = `Are You Sure You Want To Delete This Markup {ID:${data.id}}?`; - } - $UI.annotPopup.close(); - if (!confirm(message)) return; - $CAMIC.store - .deleteMark(data.oid, $D.params.data.slide) - .then((datas) => { - // server error - if (datas.error) { - const errorMessage = `${datas.text}: ${datas.url}`; - $UI.message.addError(errorMessage, 4000); - // close - return; - } - - // no data found - if (!datas.deletedCount || datas.deletedCount < 1) { - $UI.message.addWarning(`Delete Annotations Failed.`, 4000); - return; - } - - const index = $D.humanlayers.findIndex((layer) => layer.id == data.id); - - if (index == -1) return; - - data.index = index; - const layer = $D.humanlayers[data.index]; - // update UI - if (Array.isArray(layer.data)) deleteCallbackOld(data, parentType); - else deleteCallback(data, parentType); - }) - .catch((e) => { - $UI.message.addError(e); - console.error(e); - }) - .finally(() => { - console.log('delete end'); - }); + alert("Deleting is not allowed") } /** @@ -849,126 +802,7 @@ function convertGeometries(features, data) { */ function annoCallback(data) { spen.close(); - // is form ok? - const noteData = $UI.annotOptPanel._form_.value; - if ($UI.annotOptPanel._action_.disabled || noteData.name == '') { - // close layer silde - $UI.toolbar._mainTools[1].querySelector('[type=checkbox]').checked = false; - $UI.layersSideMenu.close(); - - // open app silde - $UI.toolbar._mainTools[0].querySelector('[type=checkbox]').checked = true; - $UI.appsSideMenu.open(); - - // open annotation list - // -- START QUIP550 -- // - // $UI.appsList.triggerContent('annotation','open'); - // -- END QUIP550 -- // - return; - } - // has Path? - - if ($CAMIC.viewer.canvasDrawInstance._path_index === 0) { - // toast - $UI.message.addWarning('info No Markup On Annotation. Try Holding And Dragging.'); - return; - } - - // Add new lines to notes to prevent overflow - - let str = noteData.notes || ''; - var resultString = ''; - while (typeof str==='string' && str.length > 0) { - resultString += str.substring(0, 36) + '\n'; - str = str.substring(36); - } - noteData.notes = resultString; - - // save - // provenance - Loading.open($UI.annotOptPanel.elt, 'Saving Annotation...'); - const execId = randomId(); - - const annotJson = { - creator: getUserId(), - created_date: new Date(), - provenance: { - image: { - slide: $D.params.slideId, - }, - analysis: { - source: 'human', - execution_id: execId, - name: noteData.name, - }, - }, - properties: { - annotations: noteData, - }, - geometries: ImageFeaturesToVieweportFeatures( - $CAMIC.viewer, - $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection(), - ), - }; - - // save annotation - $CAMIC.store - .addMark(annotJson) - .then((data) => { - // server error - if (data.error) { - $UI.message.addError(`${data.text}:${data.url}`); - Loading.close(); - return; - } - - // no data added - if (data.count < 1) { - Loading.close(); - $UI.message.addWarning(`Annotation Save Failed`); - return; - } - // create layer data - const newItem = { - id: execId, - name: noteData.name, - typeId: 'human', - typeName: 'human', - creator: getUserId(), - shape: annotJson.geometries.features[0].geometry.type, - data: null, - }; - $D.humanlayers.push(newItem); - $UI.layersViewer.addHumanItem(newItem, 'human', 'other'); - $UI.layersViewerMinor.addHumanItem( - newItem, - 'human', - 'other', - $minorCAMIC && $minorCAMIC.viewer ? true : false, - ); - - // data for UI - // return; - loadAnnotationById( - $CAMIC, - $UI.layersViewer.getDataItemById(execId, 'human', 'other'), - 'other', - saveAnnotCallback, - ); - if ($minorCAMIC && $minorCAMIC.viewer) { - loadAnnotationById( - $minorCAMIC, - $UI.layersViewerMinor.getDataItemById(execId, 'human', 'other'), - 'other', - null, - ); - } - }) - .catch((e) => { - Loading.close(); - console.log('save failed', e); - }) - .finally(() => {}); + console.log('save not permitted'); } function saveAnnotCallback() { @@ -2004,11 +1838,6 @@ function presetLabelOff() { canvasDraw.clear(); canvasDraw.drawOff(); $UI.appsSideMenu.close(); - $UI.toolbar - .getSubTool('preset_label') - .querySelector('input[type=checkbox]').checked = false; - $UI.toolbar.getSubTool('preset_label').querySelector('label').style.color = - ''; $UI.labelsSideMenu.close(); $CAMIC.status = null; } @@ -2204,42 +2033,7 @@ function onDeleteRuler(ruler) { } function deleteRulerHandler(execId) { - if (!confirm(message = `Are You Sure You Want To Delete This Ruler {ID:${execId}}?`)) return; - $CAMIC.store - .deleteMarkByExecId(execId, $D.params.data.slide) - .then((datas) => { - // server error - if (datas.error) { - const errorMessage = `${datas.text}: ${datas.url}`; - $UI.message.addError(errorMessage, 4000); - // close - return; - } - - // no data found - if (!datas.deletedCount || datas.deletedCount < 1) { - $UI.message.addWarning(`Delete Ruler Failed.`, 4000); - return; - } - // update UI - removeElement($D.rulerlayers, execId); - $UI.layersViewer.removeItemById(execId, 'ruler'); - $UI.layersViewerMinor.removeItemById(execId, 'ruler'); - $CAMIC.viewer.measureInstance.removeRulerById(execId); - if ($minorCAMIC && - $minorCAMIC.viewer && - $minorCAMIC.viewer.measureInstance) { - $minorCAMIC.viewer.measureInstance.removeRulerById(execId); - } - $UI.message.addSmall(`Deleted The '${execId}' Ruler.`); - }) - .catch((e) => { - $UI.message.addError(e); - console.error(e); - }) - .finally(() => { - // console.log('delete end'); - }); + alert("deletion not allowed") } @@ -2338,76 +2132,7 @@ function onAddRuler(ruler) { }; - $CAMIC.store - .addMark(rulerJson) - .then((data) => { - // server error - if (data.error) { - $UI.message.addError(`${data.text}:${data.url}`); - return; - } - - // no data added - if (data.result && data.result.ok && data.result.n < 1) { - Loading.close(); - $UI.message.addWarning(`Ruler Save Failed`); - return; - } - - const __data = data.ops[0]; - // create layer data - const newItem = { - id: execId, - name: execId, - typeId: 'ruler', - typeName: 'ruler', - data: data.ops[0], - creator: getUserId(), - }; - newItem.data.properties.innerHTML = newItem.data.properties.innerHTML.split('<').join('<'); - newItem.data.properties.innerHTML = newItem.data.properties.innerHTML.split('>').join('>'); - newItem.data.properties.innerHTML = newItem.data.properties.innerHTML.split(' ').join(' '); - newItem.data._id = {$oid: newItem.data._id}; - $D.rulerlayers.push(newItem); - $UI.layersViewer.addItem(newItem, 'ruler'); - $UI.layersViewerMinor.addItem( - newItem, - 'ruler', - $minorCAMIC && $minorCAMIC.viewer ? true : false, - ); - - const rulerData = $UI.layersViewer.getDataItemById(execId, 'ruler'); - rulerData.layer = $CAMIC.viewer.getOverlayById(ruler.element); - - const rulerDataMinor = $UI.layersViewerMinor.getDataItemById(execId, 'ruler'); - // create lay and update view - if ($minorCAMIC && $minorCAMIC.viewer && rulerDataMinor.isShow) { - const [xmin, ymin] = newItem.data.geometries.features[0].geometry.coordinates[0][0]; - const [xmax, ymax] = newItem.data.geometries.features[0].geometry.coordinates[0][2]; - rulerDataMinor.layer = $minorCAMIC.viewer.measureInstance.addRuler({ - id: newItem.id, - mode: newItem.data.properties.mode, - rect: { - x: xmin, - y: ymin, - width: xmax-xmin, - height: ymax-ymin, - }, - innerHTML: newItem.data.properties.innerHTML, - isShow: rulerDataMinor.isShow, - }); - } - - // close app side - // $UI.layersViewer.update(); - // $UI.layersViewerMinor.update(); - - $UI.message.addSmall(`Added The '${execId}' Ruler.`); - }) - .catch((e) => { - Loading.close(); - console.log('Ruler Save Failed'); - }); + console.log("rulers are not saved persistently") } async function rootCallback({root, parent, parentName, items}) { diff --git a/apps/mini/viewer.html b/apps/mini/viewer.html index 6cbb8217b..af4312def 100644 --- a/apps/mini/viewer.html +++ b/apps/mini/viewer.html @@ -233,10 +233,10 @@ type="text/javascript" src="../../components/collapsiblelist/collapsiblelist.js" > - +