From 5fd408261e8dae2d354faa58bb3c08ba41144bd6 Mon Sep 17 00:00:00 2001 From: Birm Date: Fri, 20 Oct 2023 11:59:55 -0400 Subject: [PATCH 01/49] start mini viewer --- apps/mini/dataloaders.js | 111 ++ apps/mini/init.js | 1090 ++++++++++++++++ apps/mini/tut.js | 147 +++ apps/mini/uicallbacks.js | 2670 ++++++++++++++++++++++++++++++++++++++ apps/mini/viewer.html | 470 +++++++ 5 files changed, 4488 insertions(+) create mode 100644 apps/mini/dataloaders.js create mode 100644 apps/mini/init.js create mode 100644 apps/mini/tut.js create mode 100644 apps/mini/uicallbacks.js create mode 100644 apps/mini/viewer.html diff --git a/apps/mini/dataloaders.js b/apps/mini/dataloaders.js new file mode 100644 index 000000000..de65438f5 --- /dev/null +++ b/apps/mini/dataloaders.js @@ -0,0 +1,111 @@ + + +function FormTempaltesLoader() { + function loadingFormTemplates() { + $CAMIC.store.findTemplate() + // + .then(function(temps) { + // get templates data + $D.templates = temps; + }) + // + .catch(function(error) { + // templates schema + + console.error(error); + }) + // + .finally(function() { + if ($D.templates) { + // load UI + } else { + // set message + $UI.message.addError('Loading Templates is Error'); + } + }); + } + + var checkCoreIsReady = setInterval(function() { + if ($CAMIC) { + clearInterval(checkCoreIsReady); + // load data + loadingFormTemplates(); + } + }, 500); +} + +function layersLoader() { + // human + function loadingHumanOverlayers() { + $CAMIC.store.findMarkTypes($D.params.slideId, 'human').then(function(layers) { + // convert part not nesscary + $D.humanlayers = [...layers.map(covertToHumanLayer)]; + + // add data and create ui item + addHumanLayerItems(); + }).catch(function(error) { + // overlayers schema + $UI.message.addError('Loading Human Layers is Error'); + console.error(error); + }); + } + // ruler + function loadingRulerOverlayers() { + $CAMIC.store.findMarkTypes($D.params.slideId, 'ruler').then(function(layers) { + // convert part not nesscary + $D.rulerlayers = [...layers.map(covertToRulerLayer)]; + + // add data and create ui item + addRulerLayerItems(); + }).catch(function(error) { + // overlayers schema + $UI.message.addError('Loading Ruler Layers is Error'); + console.error(error); + }); + } + // heatmap + function loadingHeatmapOverlayers() { + $CAMIC.store.findHeatmapType($D.params.slideId).then(function(layers) { + $D.heatmaplayers = []; + // convert and load heatmap layer + for (let i = 0; i < layers.length; i++) { + const item = layers[i].provenance.analysis; + $D.heatmaplayers.push({id: item.execution_id, + name: item.execution_id, + typeId: 'heatmap', + typeName: 'heatmap', + }); + } + // add data and create ui item + addHeatmapLayerItems(); + }).catch(function(error) { + // overlayers schema + $UI.message.addError('Loading heatmap Overlayers is Error'); + console.error(error); + }); + } + + // segmentation + function loadingComputerOverlayers() { + $CAMIC.store.findMarkTypes($D.params.slideId, 'computer').then(function(layers) { + // convert part not nesscary + $D.computerlayers=[...layers.map(covertToCumputerLayer)]; + // add data and create ui item + addComputerLayerItems(); + }).catch(function(error) { + $UI.message.addError('Loading Computer Layers is Error'); + console.error(error); + }); + } + var checkCoreIsReady = setInterval(function() { + if ($UI.layersViewer && $UI.layersViewerMinor) { + clearInterval(checkCoreIsReady); + loadingHumanOverlayers(); + loadingRulerOverlayers(); + loadingHeatmapOverlayers(); + loadingComputerOverlayers(); + } + }, 500); +} + + diff --git a/apps/mini/init.js b/apps/mini/init.js new file mode 100644 index 000000000..da010fe3e --- /dev/null +++ b/apps/mini/init.js @@ -0,0 +1,1090 @@ +// CAMIC is an instance of camicroscope core +// $CAMIC in there +let $CAMIC = null; +let tracker; +let $minorCAMIC = null; +// for all instances of UI components +const $UI = new Map(); + +const $D = { + pages: { + home: '../table.html', + table: '../table.html', + }, + params: null, // parameter from url - slide Id and status in it (object). + overlayers: null, // array for each layers + templates: null, // json schema for prue-form + segments: [], +}; + +window.addEventListener('keydown', (e) => { + if (!$CAMIC || !$CAMIC.viewer) return; + const key = e.key; + // escape key to close all operations + if ('escape' == key.toLocaleLowerCase()) { + magnifierOff(); + measurementOff(); + annotationOff(); + presetLabelOff(); + } + + // open annotation (ctrl + a) + if (e.ctrlKey && 'a' == key.toLocaleLowerCase() && $CAMIC.viewer.canvasDrawInstance) { + const li = $UI.toolbar.getSubTool('annotation'); + eventFire(li, 'click'); + return; + } + // open magnifier (ctrl + m) + if (e.ctrlKey && 'm' == key.toLocaleLowerCase() && $UI.spyglass) { + const li = $UI.toolbar.getSubTool('magnifier'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'change'); + return; + } + // open measurement (ctrl + r) + if (e.ctrlKey && 'r' == key.toLocaleLowerCase() && $CAMIC.viewer.measureInstance) { + e.preventDefault(); + const li = $UI.toolbar.getSubTool('measurement'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'change'); + return; + } + // open side-by-side (ctrl + s) + if (e.ctrlKey && 's' == key.toLocaleLowerCase()) { + e.preventDefault(); + const li = $UI.toolbar.getSubTool('sbsviewer'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'click'); + return; + } + // open side-by-side (ctrl + l) + if (e.ctrlKey && 'l' == key.toLocaleLowerCase()) { + e.preventDefault(); + const li = $UI.toolbar.getSubTool('preset_label'); + const chk = li.querySelector('input[type=checkbox]'); + chk.checked = !chk.checked; + eventFire(chk, 'click'); + return; + } + + // shortcuts for preset labels + if ($D.labels && + $D.labels.configuration && + Array.isArray($D.labels.configuration) && + $D.labels.configuration.length > 0 && + e.ctrlKey) { + e.key; + const elt = $UI.labelsViewer.allLabels.find((l)=>l.dataset.key&&l.dataset.key.toLowerCase()==e.key.toLowerCase()); + if (elt) { + $UI.toolbar + .getSubTool('preset_label') + .querySelector('input[type=checkbox]').checked = true; + $UI.labelsViewer.selectLabel(elt); + } + } +}); + +// initialize viewer page +function initialize() { + var checkPackageIsReady = setInterval(function() { + if (IsPackageLoading) { + clearInterval(checkPackageIsReady); + // create a viewer and set up + initCore(); + + // loading the form template data + FormTempaltesLoader(); + + // loading the overlayers data + layersLoader(); + } + }, 100); +} + +// setting core functionalities +function initCore() { + // start initial + + // create the message queue + $UI.message = new MessageQueue({position: 'bottom-left'}); + + // zoom info and mmp + const opt = { + draw: { + // extend context menu btn group + btns: [ + { + // annotation + type: 'btn', + title: 'Annotation', + class: 'material-icons', + text: 'description', + callback: saveAnnotation, + }, + { + // analytics + type: 'btn', + title: 'Analytics', + class: 'material-icons', + text: 'settings_backup_restore', + callback: saveAnalytics, + }, + ], + }, + }; + // set states if exist + if ($D.params.states) { + opt.states = $D.params.states; + } + // pathdb home directly + if ($D.params.mode == 'pathdb') { + $D.pages.home = '../../../'; + $D.pages.table = '../../../'; + } + + try { + const slideQuery = {}; + slideQuery.id = $D.params.slideId; + slideQuery.name = $D.params.slide; + slideQuery.location = $D.params.location; + opt.addRulerCallback = onAddRuler; + opt.deleteRulerCallback = onDeleteRuler; + $CAMIC = new CaMic('main_viewer', slideQuery, opt); + } catch (error) { + Loading.close(); + $UI.message.addError('Core Initialization Failed'); + console.error(error); + return; + } + + $CAMIC.loadImg(function(e) { + // image loaded + if (e.hasError) { + // if this is a retry, assume normal behavior (one retry per slide) + if ($D.params.retry) { + $UI.message.addError(e.message); + // can't reach Slide and return to home page + if (e.isServiceError) redirect($D.pages.table, e.message, 1); + } else { + // If this is our first attempt, try one more time. + let params = new URLSearchParams(window.location.search); + params.set('retry', '1'); + window.location.search = params.toString(); + } + } else { + $D.params.data = e; + // popup panel + $CAMIC.viewer.addHandler('canvas-lay-click', function(e) { + if (!e.data) { + $UI.annotPopup.close(); + return; + } + // for support QUIP 2.0 + const data = Array.isArray(e.data) ? e.data[e.data.selected] : e.data; + + const type = data.provenance.analysis.source; + let body; + let attributes; + let warning = null; + switch (type) { + case 'human': + let area; + let circumference; + if (data.geometries) { + if ( + (data.selected != null || data.selected != undefined) && + data.geometries.features[data.selected] && + data.geometries.features[data.selected].properties.area + ) { + area = `${Math.round( + data.geometries.features[data.selected].properties.area, + )} μm²`; + } + if ( + (data.selected != null || data.selected != undefined) && + data.geometries.features[data.selected] && + data.geometries.features[data.selected].properties.circumference + ) { + circumference = `${Math.round( + data.geometries.features[data.selected].properties + .circumference, + )} μm`; + } + } // othereise, don't try to calculate area and circumference + // human + + attributes = data.properties.annotations; + if (area) attributes.area = area; + if (circumference) attributes.circumference = circumference; + body = convertHumanAnnotationToPopupBody(attributes); + if ( + data.geometries && + data.geometries.features[data.selected].properties.isIntersect + ) { + warning = `
Inaccurate Area and Circumference
`; + } + if ( + data.geometries && + data.geometries.features[data.selected].properties.nommp + ) { + warning = `
This slide has no mpp
`; + } + + $UI.annotPopup.showFooter(); + break; + case 'computer': + // handle data.provenance.analysis.computation = `segmentation` + attributes = data.properties.scalar_features[0].nv; + body = {type: 'map', data: attributes}; + $UI.annotPopup.hideFooter(); + break; + default: + return; + // statements_def + break; + } + + $UI.annotPopup.data = { + id: data.provenance.analysis.execution_id, + oid: data._id.$oid, + annotation: attributes, + selected: e.data.selected, + }; + const getCateName = () => { + const items = $UI.layersViewer.setting.categoricalData.human.items; + var dataType = null; + for (const key in items) { + if ({}.hasOwnProperty.call(items, key)) { + dataType = key; + if (items.hasOwnProperty(key)&& + Array.isArray(items[key].items)&& + items[key].items.some((i)=>i.item.id == $UI.annotPopup.data.id)) break; + } + } + return dataType; + }; + + + $UI.annotPopup.dataType = null; + $UI.annotPopup.dataType = data.provenance && data.provenance.analysis && + data.provenance.analysis.source && data.provenance.analysis.source=='human'? + getCateName($UI.annotPopup.data.id):null; + + $UI.annotPopup.setTitle(`id:${data.provenance.analysis.execution_id}`); + $UI.annotPopup.setBody(body); + if (warning) $UI.annotPopup.body.innerHTML += warning; + + $UI.annotPopup.open(e.position); + }); + + // create the message bar TODO for reading slide Info TODO + $UI.slideInfos = new CaMessage({ + /* opts that need to think of*/ + id: 'cames', + defaultText: `Slide: ${$D.params.data.name}`, + }); + + // spyglass + $UI.spyglass = new Spyglass({ + targetViewer: $CAMIC.viewer, + imgsrc: $D.params.data.url, + }); + } + }); + + $CAMIC.viewer.addHandler('open', function() { + $CAMIC.viewer.canvasDrawInstance.addHandler('start-drawing', startDrawing); + $CAMIC.viewer.canvasDrawInstance.addHandler('stop-drawing', stopDrawing); + // init UI -- some of them need to wait data loader to load data + // because UI components need data to initialize + initUIcomponents(); + // action tracker start + tracker = new Tracker($CAMIC, $D.params.data._id.$oid, getUserId()); + tracker.start(); + }); +} + +// initialize all UI components +async function initUIcomponents() { + /* create UI components */ + $UI.downloadSelectionModal = new ModalBox({ + id: 'downloadSelectionModal', + hasHeader: true, + headerText: 'Download Selection', + hasFooter: true, + }); + const header = $UI.downloadSelectionModal.elt.querySelector('.modalbox-header'); + header.querySelector('span.close').style.display = 'None'; + const footer = $UI.downloadSelectionModal.elt.querySelector('.modalbox-footer'); + const cancelBtn = document.createElement('button'); + cancelBtn.classList.add('btn'); + cancelBtn.classList.add('btn-sm'); + cancelBtn.classList.add('btn-secondary'); + cancelBtn.textContent = 'Cancel'; + const downloadBtn = document.createElement('button'); + downloadBtn.classList.add('btn'); + downloadBtn.classList.add('btn-sm'); + downloadBtn.classList.add('btn-info'); + downloadBtn.textContent = 'Download'; + footer.innerHTML = ''; + footer.classList.add('footer'); + footer.appendChild(cancelBtn); + footer.appendChild(downloadBtn); + + cancelBtn.addEventListener('click', hideDownloadSelection); + downloadBtn.addEventListener('click', downloadSelection); + + + $UI.modalbox = new ModalBox({ + id: 'modalbox', + hasHeader: true, + headerText: 'HeatMap List', + hasFooter: false, + }); + + const subToolsOpt = []; + // home + if (ImgloaderMode == 'iip') { + subToolsOpt.push({ + name: 'home', + icon: 'home', // material icons' name + title: 'Home', + type: 'btn', // btn/check/dropdown + value: 'home', + 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({ + name: 'magnifier', + icon: 'search', + title: 'Magnifier', + type: 'dropdown', + value: 'magn', + dropdownList: [ + { + value: 0.5, + title: '0.5', + checked: true, + }, + { + value: 1, + title: '1.0', + }, + { + value: 2, + title: '2.0', + }, + ], + callback: toggleMagnifier, + }); + // measurement tool + if ($CAMIC.viewer.measureInstance) { + subToolsOpt.push({ + name: 'measurement', + icon: 'straighten', + title: 'Measurement', + type: 'dropdown', + value: 'measure', + dropdownList: [ + { + value: 'straight', + title: 'straight', + icon: 'straighten', + checked: true, + }, + { + value: 'coordinate', + title: 'coordinate', + icon: 'square_foot', + }, + ], + 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', + icon: 'invert_colors', + title: 'Enhance', + type: 'dropdown', + value: 'Enhance', + dropdownList: [ + { + value: 'Histogram Eq', + title: 'Histogram Equalization', + icon: 'leaderboard', + checked: true, + }, + { + value: 'Edge', + title: 'Edge', + icon: 'show_chart', + }, + { + value: 'Sharpen', + title: 'Sharpen', + icon: 'change_history', + }, + { + value: 'Custom', + title: 'Custom', + icon: 'api', + }, + ], + 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', + 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'; + } + }, + }); + + // -- 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', + type: 'btn', + value: 'slCap', + callback: captureSlide, + }); + subToolsOpt.push({ + name: 'tutorial', + icon: 'help', + title: 'Tutorial', + value: 'tutorial', + type: 'btn', + callback: function() { + tour.init(); + tour.start(true); + }, + }); + + // Additional Links handler + function additionalLinksHandler(url, openInNewTab, appendSlide) { + if (appendSlide === true) { + url = url + '?slide=' + $D.params.slideId; + url = url + '&state=' + StatesHelper.encodeStates(StatesHelper.getCurrentStates()); + } + if (openInNewTab === true) { + window.open(url, '_blank').focus(); + } else { + window.location.href = url; + } + } + var additionalLinksConfig = await $CAMIC.store.getConfigByName('additional_links') + .then((list)=>list.length==0?null:list[0]); + if (additionalLinksConfig&&additionalLinksConfig.configuration&&Array.isArray(additionalLinksConfig.configuration)) { + additionalLinksConfig.configuration.forEach((link)=>{ + var openInNewTab = link.openInNewTab === false ? false : true; + var appendSlide = link.appendSlide === true ? true : false; + var url = link.url; + subToolsOpt.push({ + name: link.displayName, + icon: link.icon ? link.icon : 'link', + title: link.displayName, + value: link.displayName, + type: 'btn', + callback: function() { + additionalLinksHandler(url, openInNewTab, appendSlide); + }, + }); + }); + } + + // create the tool bar + $UI.toolbar = new CaToolbar({ + /* opts that need to think of*/ + id: 'ca_tools', + zIndex: 601, + mainToolsCallback: mainMenuChange, + subTools: subToolsOpt, + }); + + // create two side menus for tools + $UI.appsSideMenu = new SideMenu({ + id: 'side_apps', + width: 300, + // , isOpen:true + callback: toggleSideMenu, + }); + + $UI.layersSideMenu = new SideMenu({ + id: 'side_layers', + width: 250, + contentPadding: 5, + // , isOpen:true + callback: toggleSideMenu, + }); + + const loading = `
loading layers...
`; + $UI.layersSideMenu.addContent(loading); + // TODO add layer viewer + + + /* annotation popup */ + $UI.annotPopup = new PopupPanel({ + footer: [ + // { // edit + // title:'Edit', + // class:'material-icons', + // text:'notes', + // callback:annoEdit + // }, + { + // delete + title: 'Delete', + class: 'material-icons', + text: 'delete_forever', + callback: annoDelete, + }, + ], + }); + + // TODO -- labels // + $UI.labelsSideMenu = new SideMenu({ + id: 'labels_layers', + width: 180, + contentPadding: 5, + }); + var labelsTitle = document.createElement('div'); + labelsTitle.classList.add('item_head'); + labelsTitle.textContent = 'Label Manager'; + + $UI.labelsSideMenu.addContent(labelsTitle); + + $D.labels = await $CAMIC.store.getConfigByName('preset_label').then((list)=>list.length==0?null:list[0]); + + + // onAdd() + // onRemove(labels) + // onUpdate(labels) + // onSelected() + $UI.labelsViewer = new LabelsViewer({ + id: 'labelmanager', + data: $D.labels?$D.labels.configuration:[], + onAdd: addPresetLabelsHandler, + onEdit: editPresetLabelsHandler, + onRemove: removePresetLabelsHandler, + onSelected: selectedPresetLabelsHandler, + }, + ); + $UI.labelsViewer.elt.parentNode.removeChild($UI.labelsViewer.elt); + $UI.labelsSideMenu.addContent($UI.labelsViewer.elt); + + // == end -- // + + var checkOverlaysDataReady = setInterval(function() { + if ( + $D.params.data && + $CAMIC && + $CAMIC.viewer && + $CAMIC.viewer.omanager + ) { + clearInterval(checkOverlaysDataReady); + // for segmentation + $CAMIC.viewer.createSegment({ + store: $CAMIC.store, + slide: $D.params.data.slide, + data: [], + }); + + // create control + + // TODO move to add layers + // create main layer viewer items with states + // const mainViewerData = $D.overlayers.map((d) => { + // const isShow = + // $D.params.states && + // $D.params.states.l && + // $D.params.states.l.includes(d.id) ? + // true : + // false; + // return {item: d, isShow: isShow}; + // }); + + + // TODO move to add layers + // create monir layer viewer items + // const minorViewerData = $D.overlayers.map((d) => { + // return {item: d, isShow: false}; + // }); + + // create UI and set data + $UI.layersViewer = createLayerViewer( + 'overlayers', + null, + callback.bind('main'), + rootCallback.bind('main'), + ); + // create UI and set data - minor + $UI.layersViewerMinor = createLayerViewer( + 'overlayersMinor', + null, + callback.bind('minor'), + rootCallback.bind('minor'), + ); + + // TODO move to add layers + // if ($D.params.states && $D.params.states.l) { + // $D.params.states.l.forEach((id) => + // loadAnnotationById($CAMIC, $UI.layersViewer.getDataItemById(id), null), + // ); + // } + + $UI.layersList = new CollapsibleList({ + id: 'layerslist', + list: [ + { + id: 'left', + title: 'Left Viewer', + content: 'No Template Loaded', // $UI.annotOptPanel.elt + // isExpand:true + }, + { + id: 'right', + title: 'Right Viewer', + content: 'No Template Loaded', // $UI.algOptPanel.elt, + }, + ], + changeCallBack: function(e) { + // console.log(e); + }, + }); + $UI.layersSideMenu.clearContent(); + // add to layers side menu + const title = document.createElement('div'); + title.classList.add('item_head'); + title.textContent = 'Layers Manager'; + + $UI.layersSideMenu.addContent(title); + + // loading status + $UI.loadStatus = document.createElement('div'); + $UI.loadStatus.style.display = 'none'; + $UI.loadStatus.classList.add('load-status'); + $UI.loadStatus.innerHTML = `
cached
Loading
`; + $UI.layersSideMenu.addContent($UI.loadStatus); + + // zoom locker control + $UI.lockerPanel = document.createElement('div'); + $UI.lockerPanel.classList.add('lock_panel'); + $UI.lockerPanel.style.display = 'none'; + $UI.lockerPanel.innerHTML = ``; + $UI.lockerPanel + .querySelector('input[type=checkbox]') + .addEventListener('change', (e) => { + isLock = !isLock; + if (isLock) { + $minorCAMIC.viewer.viewport.zoomTo( + $CAMIC.viewer.viewport.getZoom(true), + $CAMIC.viewer.viewport.getCenter(true), + true, + ); + $CAMIC.viewer.controls.bottomright.style.display = 'none'; + } else { + $CAMIC.viewer.controls.bottomright.style.display = ''; + } + }); + + $UI.layersSideMenu.addContent($UI.lockerPanel); + + $UI.layersList.clearContent('left'); + $UI.layersList.addContent('left', $UI.layersViewer.elt); + $UI.layersList.clearContent('right'); + $UI.layersList.addContent('right', $UI.layersViewerMinor.elt); + + $UI.layersList.elt.parentNode.removeChild($UI.layersList.elt); + closeMinorControlPanel(); + $UI.layersSideMenu.addContent($UI.layersList.elt); + } + }, 300); + + var checkTemplateSchemasDataReady = setInterval(function() { + if ($D.templates) { + clearInterval(checkTemplateSchemasDataReady); + const annotRegex = new RegExp('annotation', 'gi'); + const annotSchemas = $D.templates.filter((item) => + item.id.match(annotRegex), + ); + /* annotation control */ + $UI.annotOptPanel = new OperationPanel({ + // id: + // element: + formSchemas: annotSchemas, + resetCallback: resetCallback, + action: { + title: 'Save', + callback: annoCallback, + }, + }); + + const title = document.createElement('div'); + title.classList.add('item_head'); + title.textContent = 'Annotation'; + $UI.appsSideMenu.addContent(title); + $UI.annotOptPanel.elt.classList.add('item_body'); + $UI.appsSideMenu.addContent($UI.annotOptPanel.elt); + } + }, 300); +} +function createLayerViewer(id, viewerData, callback, rootCallback) { + const layersViewer = new LayersViewer({ + id: id, + data: viewerData, + removeCallback: removeCallback, + locationCallback: locationCallback, + callback: callback, + rootCallback: rootCallback, + + }); + layersViewer.elt.parentNode.removeChild(layersViewer.elt); + return layersViewer; +} + +// create lay panel for side-by-side control +function createLayPanelControl() { + $UI.layCtrlbar = document.createElement('div'); + $UI.layCtrlbar.style = ` + display:none; + margin: .2rem; + background-color: #365f9c; + cursor: default; + padding: .5rem; + width: 100%; + border: none; + text-align: left; + outline: none; + font-size: 1.2rem;`; + + createRadios(); + $UI.layersSideMenu.addContent($UI.layCtrlbar); + // control + const radios = $UI.layCtrlbar.querySelectorAll('input[name=ctrlPane]'); + radios.forEach((r) => { + r.addEventListener('change', function(e) { + const val = e.target.value; + switch (val) { + case 'main': + $UI.layersViewer.elt.style.display = 'flex'; + $UI.layersViewerMinor.elt.style.display = 'none'; + break; + case 'minor': + $UI.layersViewer.elt.style.display = 'none'; + $UI.layersViewerMinor.elt.style.display = 'flex'; + break; + default: + // statements_def + break; + } + }); + }); +} + +function createRadios() { + const temp = ` + + + + + `; + $UI.layCtrlbar.innerHTML = temp; +} + +function redirect(url, text = '', sec = 5) { + let timer = sec; + if (!timer) { + window.location.href = url; + } + setInterval(function() { + if (!timer) { + window.location.href = url; + } + if (Loading.instance && Loading.instance.parentNode) { + Loading.text.textContent = `${text} ${timer}s.`; + } else { + Loading.open(document.body, `${text} ${timer}s.`); + } + // Hint Message for clients that page is going to redirect to Flex table in 5s + timer--; + }, 1000); +} + +function updateSlideView() { + if (!confirm(`Do you want to mark this slide as reviewed?`)) return; + Loading.open(document.body, 'changing review status ...'); + $CAMIC.store.updateSlideReview($D.params.slideId, 'true').then(function(e) { + if (e.status==200) { + $UI.toolbar.getSubTool('review').style.display = 'none'; + } + }).finally(function() { + Loading.close(); + }); +} + + +function addHumanLayerItems() { + const mainViewerItems = { + 'other': { + item: { + id: 'other', + name: 'other', + }, + items: [], + }, + }; + + $D.humanlayers.reduce((items, d)=> { + const isShow = + $D.params.states && + $D.params.states.l && + $D.params.states.l.includes(d.id) ? + true: + false; + if (d.label&&d.label.id&&d.label.name) { // preset label + const {id, name} = d.label; + if (!items[id]) { + items[id] = { + item: { + id: id, + name: name, + }, + items: [], + }; + } + items[id].items.push({item: d, isShow}); + } else { // other + items['other'].items.push({item: d, isShow}); + } + return items; + }, mainViewerItems); + + $UI.layersViewer.addHumanItems(mainViewerItems); + + // minor viewer minorViewer + const minorViewerItems = { + 'other': { + item: { + id: 'other', + name: 'other', + }, + items: [], + }, + }; + $D.humanlayers.reduce((items, d)=> { + const isShow = + $D.params.states && + $D.params.states.l && + $D.params.states.l.includes(d.id) ? + true: + false; + if (d.label&&d.label.id&&d.label.name) { + + } + if (d.label&&d.label.id&&d.label.name) { // preset label + const {id, name} = d.label; + if (!items[id]) { + items[id] = { + item: { + id: id, + name: name, + }, + items: [], + }; + } + items[id].items.push({item: d, isShow}); + } else { // other + items['other'].items.push({item: d, isShow}); + } + return items; + }, minorViewerItems); + + $UI.layersViewerMinor.addHumanItems(minorViewerItems); +} +function openLoadStatus(text) { + const txt = $UI.loadStatus.querySelector('.text'); + txt.textContent = `Loading ${text}`; + $UI.loadStatus.style.display = null; +} +function closeLoadStatus() { + $UI.loadStatus.style.display = 'none'; +} +function addRulerLayerItems(data) { + const mainViewerData = $D.rulerlayers.map((d) => { + return {item: d, isShow: false}; + }); + // create monir layer viewer items + const minorViewerData = $D.rulerlayers.map((d) => { + return {item: d, isShow: false}; + }); + $UI.layersViewer.addItems(mainViewerData, 'ruler'); + $UI.layersViewerMinor.addItems(minorViewerData, 'ruler'); +} + +function addComputerLayerItems(data) { + const mainViewerData = $D.computerlayers.map((d) => { + return {item: d, isShow: false}; + }); + // create monir layer viewer items + const minorViewerData = $D.computerlayers.map((d) => { + return {item: d, isShow: false}; + }); + $UI.layersViewer.addItems(mainViewerData, 'segmentation'); + $UI.layersViewerMinor.addItems(minorViewerData, 'segmentation'); +} + +function addHeatmapLayerItems(data) { + const mainViewerData = $D.heatmaplayers.map((d) => { + return {item: d, isShow: false}; + }); + // create monir layer viewer items + const minorViewerData = $D.heatmaplayers.map((d) => { + return {item: d, isShow: false}; + }); + $UI.layersViewer.addItems(mainViewerData, 'heatmap'); + $UI.layersViewerMinor.addItems(minorViewerData, 'heatmap'); +} + +// const mainViewerData = $D.overlayers.map((d) => { +// const isShow = +// $D.params.states && +// $D.params.states.l && +// $D.params.states.l.includes(d.id) ? +// true : +// false; +// return {item: d, isShow: isShow}; +// }); + + +// TODO move to add layers +// create monir layer viewer items +// const minorViewerData = $D.overlayers.map((d) => { +// return {item: d, isShow: false}; +// }); diff --git a/apps/mini/tut.js b/apps/mini/tut.js new file mode 100644 index 000000000..8e135ad9c --- /dev/null +++ b/apps/mini/tut.js @@ -0,0 +1,147 @@ +var tour = new Tour({ + name: 'Tour', + steps: [{ + element: 'label[title=\'Applications\']', + title: 'Annotations', + content: 'Opens the Annotation panel,'+ + ' where you can select which annotation set to view,'+ + ' name that annotation set, add optional notes about'+ + ' the annotation set, save the annotation set, and reset the panel to its original state.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Layers\']', + title: 'Layer Manager', + content: 'Opens the Layers Manager panel,'+ + ' where you can select which layers to view.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Home\']', + title: 'Home', + content: 'Return to the data table so that'+ + ' you can open another slide.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Draw\']', + title: 'Draw', + content: 'Draw thin lines, thick lines, or polygons on the image.'+ + ' Annotations can also be computer aided using the Smart-pen tool. Draw them, stretch them, remove them.'+ + ' To maintain the integrity of measurements, avoid drawing shapes that overlap or intersect one another.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Magnifier\']', + title: 'Magnifier', + content: 'The Magnifier works like a magnifying glass and allows'+ + ' you to see the slide at normal magnification (1.0), low magnification (0.5),'+ + ' or high magnification (2.0). Click a magnification level and place the'+ + ' bounding box on the area of the slide you want to magnify.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Measurement\']', + title: 'Measurement', + content: 'Drag this tool on the slide to learn the measurement in micrometers.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Share View\']', + title: 'Share View', + content: 'Opens a window with a URL to the current presentation state of the'+ + ' slide including the magnification level, layers that are currently open, and your position on the image.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'label[title=\'Side By Side Viewer\']', + title: 'Side By Side Viewer', + content: 'Shows the Layer Manager panel, the left and right'+ + ' layers, and inset window. For the right and left layer, select which layer you want to view.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Heat Map\']', + title: 'Heat Map', + content: 'For a slide with heatmap data, opens the choices of heatmaps available,'+ + ' as well as ways of displaying the heatmaps.'+ + ' The gradient shows all of the values on the selected spectrum for the field you selected.'+ + ' Contains a heatmap edit pen function.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Labeling\']', + title: 'Labeling', + content: 'Use this tool to draw a circle or rectangle around a tumor region,'+ + ' measure an area on the slide, and download labels.'+ + ' 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, and download labels.'+ + ' Click the left arrow at the far right of the toolbar to hide it,'+ + ' then click the right arrow to show it.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Segment\']', + title: 'Segment', + content: 'This tool allows you to display, count, and export nuclear segmentations on the image.'+ + ' Clicking this tool opens the following custom toolbar.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Predict\']', + title: 'Model', + content: 'Show results from a pre-trained tensorflow compatible model on a ROI of the slide.', + 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=\'Slide Capture\']', + title: 'Slide Capture', + content: 'Take Screenshot of the slide.', + placement: 'auto bottom', + smartPlacement: true, + }, + { + element: 'i[title=\'Tutorial\']', + title: 'Tutorial', + content: 'Click here to start viewer Tour.', + placement: 'auto bottom', + smartPlacement: true, + }], + template: '
'+ + '

'+ + '

'+ + '
'+ + '
|'+ + '
'+ + '
'+ + '
', + backdrop: true, + // resets tour always to resume always from the beginning + onEnd: function reset() { + window.localStorage.setItem('Tour_current_step', 0); + }, +}); +tour.init(); diff --git a/apps/mini/uicallbacks.js b/apps/mini/uicallbacks.js new file mode 100644 index 000000000..aea890805 --- /dev/null +++ b/apps/mini/uicallbacks.js @@ -0,0 +1,2670 @@ +/* +UI and Core callback and hook +all functions help the UI and Core part together that makes workflows. +*/ + +// UI callback functions list + +/** + * Toggles side-by-side viewer + * @param {Object} opt + */ +function toggleViewerMode(opt) { + if (opt.checked) { + // turn off preset label + toolsOff(); + + // turn off magnifier + // magnifierOff(); + // turn off measurement + // measurementOff(); + + // open layers menu + $UI.toolbar._mainTools[1].querySelector( + 'input[type=checkbox]', + ).checked = true; + $UI.layersSideMenu.open(); + + // close apps menu + $UI.toolbar._mainTools[0].querySelector( + 'input[type=checkbox]', + ).checked = false; + $UI.appsSideMenu.close(); + + openMinorControlPanel(); + openSecondaryViewer(); + } else { + $UI.appsSideMenu.close(); + $UI.layersSideMenu.close(); + closeMinorControlPanel(); + closeSecondaryViewer(); + } +} + +/** + * Finds the slide for minor-viewer in side-by-side viewer + * @param {Object} size object with height and width + */ +function multSelectorAction(size) { + // hidden main viewer's bottom right control and get navigator + $CAMIC.viewer.controls.bottomright.style.display = 'none'; + $UI.lockerPanel.style.display = ''; + $UI.lockerPanel.querySelector('input[type=checkbox]').checked = true; + isLock = true; + // open new instance camic + try { + const slideQuery = {}; + slideQuery.id = $D.params.slideId; + slideQuery.name = $D.params.slide; + slideQuery.location = $D.params.location; + $minorCAMIC = new CaMic('minor_viewer', slideQuery, { + // osd options + // mouseNavEnabled:false, + // panVertical:false, + // panHorizontal:false, + // showNavigator:false, + // customized options + // hasZoomControl:false, + hasDrawLayer: false, + addRulerCallback: onAddRuler, + deleteRulerCallback: onDeleteRuler, + // hasLayerManager:false, + // hasScalebar:false, + // states options + navigatorHeight: size.height, + navigatorWidth: size.width, + states: { + x: $CAMIC.viewer.viewport.getCenter().x, + y: + $CAMIC.viewer.viewport.getCenter().y * + $CAMIC.viewer.imagingHelper.imgAspectRatio, + z: $CAMIC.viewer.viewport.getZoom(), + }, + }); + + // synchornic zoom and move + // coordinated Viewer - zoom + $CAMIC.viewer.addHandler('zoom', synchornicView1, {type: 'zoom'}); + + // coordinated Viewer - pan + $CAMIC.viewer.addHandler('pan', synchornicView1, {type: 'pan'}); + + // loading image + $minorCAMIC.loadImg(function(e) { + // image loaded + if (e.hasError) { + $UI.message.addError(e.message); + } + }); + $minorCAMIC.viewer.addOnceHandler('tile-drawing', function() { + $minorCAMIC.viewer.addHandler('zoom', synchornicView2, {type: 'zoom'}); + $minorCAMIC.viewer.addHandler('pan', synchornicView2, {type: 'pan'}); + // create segment display + $minorCAMIC.viewer.createSegment({ + store: $minorCAMIC.store, + slide: $D.params.data.name, + data: [], + }); + }); + } catch (error) { + Loading.close(); + console.error(error); + $UI.message.addError('Core Initialization Failed'); + return; + } +} + +var active1 = false; +var active2 = false; +var isLock = true; +function synchornicView1(data) { + if (active2) return; + active1 = true; + switch (data.userData.type) { + case 'zoom': + if (isLock) { + $minorCAMIC.viewer.viewport.zoomTo(data.zoom, data.refPoint); + } else { + $minorCAMIC.viewer.viewport.panTo( + $CAMIC.viewer.viewport.getCenter(true), + ); + } + break; + case 'pan': + $minorCAMIC.viewer.viewport.panTo(data.center); + break; + default: + $minorCAMIC.viewer.viewport.zoomTo($CAMIC.viewer.viewport.getZoom()); + $minorCAMIC.viewer.viewport.panTo($CAMIC.viewer.viewport.getCenter()); + break; + } + active1 = false; +} + +function synchornicView2(data) { + if (active1) return; + active2 = true; + switch (data.userData.type) { + case 'zoom': + if (isLock) { + $CAMIC.viewer.viewport.zoomTo(data.zoom, data.refPoint); + } else { + $CAMIC.viewer.viewport.panTo( + $minorCAMIC.viewer.viewport.getCenter(true), + ); + } + break; + case 'pan': + $CAMIC.viewer.viewport.panTo(data.center); + break; + default: + $CAMIC.viewer.viewport.zoomTo($minorCAMIC.viewer.viewport.getZoom()); + $CAMIC.viewer.viewport.panTo($minorCAMIC.viewer.viewport.getCenter()); + break; + } + + active2 = false; +} + +/** + * Opens the side-by-side secondary viewer + */ +function openSecondaryViewer() { + // ui changed + const main = document.getElementById('main_viewer'); + const minor = document.getElementById('minor_viewer'); + main.classList.remove('main'); + main.classList.add('left'); + + minor.classList.remove('none'); + // minor.classList.add('display'); + minor.classList.add('right'); + + const navSize = { + height: $CAMIC.viewer.controls.bottomright.querySelector('.navigator').style + .height, + width: $CAMIC.viewer.controls.bottomright.querySelector('.navigator').style + .width, + }; + setTimeout(function() { + multSelectorAction(navSize); + }, 100); +} + +/** + * Closes the side-by-side secondary viewer + */ +function closeSecondaryViewer() { + // ui changed + const main = document.getElementById('main_viewer'); + const minor = document.getElementById('minor_viewer'); + main.classList.add('main'); + main.classList.remove('left'); + minor.classList.add('none'); + minor.classList.remove('right'); + $CAMIC.viewer.controls.bottomright.style.display = ''; + $UI.lockerPanel.style.display = 'none'; + + const li = $UI.toolbar.getSubTool('sbsviewer'); + li.querySelector('input[type="checkbox"]').checked = false; + Loading.close(); + + // destory + if ($minorCAMIC) { + // remove handler + $CAMIC.viewer.removeHandler('zoom', synchornicView1); + $CAMIC.viewer.removeHandler('pan', synchornicView1); + + // destroy object + $minorCAMIC.destroy(); + $minorCAMIC = null; + } + + // hide on layersViewer TODO + $UI.layersViewerMinor.toggleAllItems(); + $UI.layersViewerMinor.setting.data.forEach((d) => delete d.layer); +} + +/** + * side menu toggle callback, called from layerManager and annotation side menu + * @param {Object} opt + */ +function toggleSideMenu(opt) { + if (!opt.isOpen) { + const id = opt.target.id.split('_')[1]; + $UI.toolbar.changeMainToolStatus(id, false); + } +} + +/** + * side menu toggle callback, called from layerManager and annotation side menu + * @param {Object} opt + */ + +/** + * Home callback for redirecting to table page + * @param {Object} data + */ +function goHome(data) { + redirect($D.pages.home, `GO Home Page`, 0); +} + +// --- Annotation Tool ---// +// pen draw callback +const label = document.createElement('div'); +label.style.transformOrigin = 'center'; +label.style.height = 0; +label.style.width = 0; + +function draw(e) { + if (!$CAMIC.viewer.canvasDrawInstance) { + alert('Draw Doesn\'t Initialize'); + return; + } + const state = +e.state; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + const target = this.srcElement || this.target || this.eventSource.canvas; + if (state) { + // on + + // off magnifier + // magnifierOff(); + // off measurement + // measurementOff(); + + // turn off annotaiton + if (state==1) { + // show pop-up message to user + $UI.message.add('infoFor More Options, Right Click Anywhere On The Image', 4000); + } + if ($CAMIC.status == 'normal') { + annotationOn.call(this, state, target); + return; + } + toolsOff(); + var checkAllToolsOff = setInterval( + function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + annotationOn.call(this, state, target); + } + }.bind(this), + 100, + ); + } else { + // off + annotationOff(); + } +} + +/** + * utility function for switching off given tool + */ +function toolsOff() { + switch ($CAMIC.status) { + case 'magnifier': + magnifierOff(); + break; + case 'measure': + measurementOff(); + break; + case 'normal': + annotationOff(); + break; + case 'label': + presetLabelOff(); + break; + case 'download_selection': + downloadSelectionOff(); + break; + } +} + +function annotationOn(state, target) { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + const li = $UI.toolbar.getSubTool('annotation'); + const {style, model} = $CAMIC.drawContextmenu.getStyle(); + + canvasDraw.drawMode = model; + canvasDraw.style.color = style.color; + + li.appendChild(label); + switch (state) { + case 1: + spen.menu(65, 0.2); + spen.undo.onclick=()=>canvasDraw.__align_undo(); + // open menu + $UI.annotOptPanel._action_.style.display = ''; + label.style.transform = 'translateY(-12px) translateX(18px)'; + label.textContent = '1'; + label.style.color = ''; + break; + case 2: + $UI.annotOptPanel._action_.style.display = ''; + label.style.transform = + ' rotate(-90deg) translateX(2px) translateY(13px)'; + label.textContent = '8'; + label.style.color = 'white'; + break; + default: + // statements_def + break; + } + + canvasDraw.drawOn(); + $CAMIC.drawContextmenu.on(); + $CAMIC.drawContextmenu.open({ + x: this.clientX, + y: this.clientY, + target: target, + }); + $CAMIC.status = 'normal'; + // close layers menu + $UI.layersSideMenu.close(); + // open annotation menu + $UI.appsSideMenu.open(); + // -- START QUIP550 -- // + // $UI.appsList.triggerContent('annotation','open'); + // -- END QUIP550 -- // + const input = $UI.annotOptPanel._form_.querySelector('#name'); + input.focus(); + input.select(); +} + +function annotationOff() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + + if ( + canvasDraw._draws_data_.length && + confirm(`Do You Want To Save Annotation Before You Leave?`) + ) { + saveAnnotation(); + } else { + spen.close(); + // close menu + canvasDraw.clear(); + canvasDraw.drawOff(); + $CAMIC.drawContextmenu.off(); + $UI.appsSideMenu.close(); + toggleOffDrawBtns(); + $CAMIC.status = null; + } +} + +function toggleOffDrawBtns() { + const li = $UI.toolbar.getSubTool('annotation'); + const lab = li.querySelector('label'); + const state = +lab.dataset.state; + lab.classList.remove(`s${state}`); + lab.dataset.state = 0; + lab.classList.add(`s0`); + if (label.parentNode) li.removeChild(label); +} + + +function brushOn(d) { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const bctrl = document.getElementById('bctrl'); + bctrl.style.display = 'block'; + + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + canvasDraw.drawMode = d.mode; + canvasDraw.size = [parseInt(d.size), parseInt(d.size)]; + canvasDraw.style.color = d.color; + canvasDraw.brushType = d.type; + canvasDraw.drawOn(); + $CAMIC.status = 'brush'; + + $UI.toolbar.getSubTool('brush').querySelector('label').style.color = d.color; + // //close layers menu + // $UI.layersSideMenu.close(); +} + + +// --- Measurement Tool ---// +/** + * Callback measurement tool to toggle measurement tool + * @param {Object} data + */ +function toggleMeasurement(data) { + if (!$CAMIC.viewer.measureInstance) { + console.warn('No Measurement Tool'); + return; + } + // $UI.message.add(`Measument Tool ${data.checked?'ON':'OFF'}`); + if (data.checked) { + // trun off the main menu + $UI.layersSideMenu.close(); + if ($CAMIC.status == 'measure') { + $CAMIC.viewer.measureInstance.mode = data.status; + measurementOn(); + return; + } + // turn off annotation + toolsOff(); + var checkAllToolsOff = setInterval(function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + $CAMIC.viewer.measureInstance.mode = data.status; + measurementOn(); + } + }, 100); + // turn off magnifier + // magnifierOff(); + } else { + measurementOff(); + } +} + +/** + * switches download Selection tool on, called from toggleMeasurement + */ +function downloadSelectionOn() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + $CAMIC.viewer.canvasDrawInstance.drawOn(); + getDrawOption(); + $CAMIC.viewer.canvasDrawInstance.drawMode = 'rect'; + $CAMIC.viewer.canvasDrawInstance.style.color = '#000'; + const li = $UI.toolbar.getSubTool('download_selection'); + li.querySelector('input[type=checkbox]').checked = true; + $CAMIC.status = 'download_selection'; +} + +/** + * switches downloadSelection tool off, called from toggleMeasurement + */ +function downloadSelectionOff() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + $CAMIC.viewer.canvasDrawInstance.drawOff(); + $CAMIC.viewer.canvasDrawInstance.clear(); + setDrawOption(); + const li = $UI.toolbar.getSubTool('download_selection'); + li.querySelector('input[type=checkbox]').checked = false; + $CAMIC.status = null; +} + +/** + * switches measuring tool on, called from toggleMeasurement + */ +function measurementOn() { + if (!$CAMIC.viewer.measureInstance) return; + $CAMIC.viewer.measureInstance.on(); + const li = $UI.toolbar.getSubTool('measurement'); + li.querySelector('input[type=checkbox]').checked = true; + const value = li.querySelector('.drop_down input[type=radio]:checked').value; + if (value=='straight') { + li.querySelector('label').textContent = 'straighten'; + } else if (value=='coordinate') { + li.querySelector('label').textContent = 'square_foot'; + } + $CAMIC.status = 'measure'; +} + +/** + * switches measuring tool off, called from toggleMeasurement + */ +function measurementOff() { + if (!$CAMIC.viewer.measureInstance) return; + $CAMIC.viewer.measureInstance.off(); + const li = $UI.toolbar.getSubTool('measurement'); + li.querySelector('input[type=checkbox]').checked = false; + $CAMIC.status = null; +} + +// --- Magnifier tool ---// +/** + * Callback magnifier tool to toggle measurement tool + * @param {Object} data + */ +function toggleMagnifier(data) { + if (data.checked) { + if ($CAMIC.status == 'magnifier') { + // all tool has turn off + clearInterval(checkAllToolsOff); + magnifierOn(+data.status, this.clientX, this.clientY); + // turn off the main menu + $UI.layersSideMenu.close(); + $UI.appsSideMenu.close(); + return; + } + // annotation off + toolsOff(); + var checkAllToolsOff = setInterval( + function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + magnifierOn(+data.status, this.clientX, this.clientY); + // trun off the main menu + $UI.layersSideMenu.close(); + $UI.appsSideMenu.close(); + } + }.bind(this), + 100, + ); + // measurement off + // measurementOff(); + } else { + magnifierOff(); + } +} + +/** + * switches magnifier tool on, called from toggleMagnifier + */ +function magnifierOn(factor = 1, x = 0, y = 0) { + if (!$UI.spyglass) return; + $UI.spyglass.factor = factor; + $UI.spyglass.open(x, y); + const li = $UI.toolbar.getSubTool('magnifier'); + li.querySelector('input[type=checkbox]').checked = true; + $CAMIC.status = 'magnifier'; +} + +/** + * switches magnifier tool off, called from toggleMagnifier + */ +function magnifierOff() { + if (!$UI.spyglass) return; + $UI.spyglass.close(); + const li = $UI.toolbar.getSubTool('magnifier'); + li.querySelector('input[type=checkbox]').checked = false; + $CAMIC.status = null; +} + +// image download +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]) { + if (response[0]['filepath']) { + return response[0]['filepath']; + } + let location = response[0]['location']; + return location.substring( + location.lastIndexOf('/') + 1, + location.length, + ); + } else { + throw new Error('Slide not found'); + } + }) + .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); + }); +} + +/** + * share url callback + * @param {Object} data + */ +function shareURL(data) { + const URL = StatesHelper.getCurrentStatesURL(true); + window.prompt('Share this link', URL); +} + +/** + * main menu changed callback, toggles Layer Manager side app + * @param {Object} data + */ +function mainMenuChange(data) { + if (data.apps) { + $UI.appsSideMenu.open(); + } else { + $UI.appsSideMenu.close(); + } + + if (data.layers) { + $UI.layersSideMenu.open(); + } else { + $UI.layersSideMenu.close(); + } + + if (data.labels) { + $UI.labelsSideMenu.open(); + } else { + presetLabelOff(); + } +} + +function convertHumanAnnotationToPopupBody(notes) { + const rs = {type: 'map', data: []}; + for (let field in notes) { + if (notes.hasOwnProperty(field)) { + const val = notes[field]; + field = field + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + rs.data.push({key: field, value: val}); + } + } + return rs; +} + +/** + * Deletes the annotation with given data + * called from removeCallback + * @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'); + }); +} + +/** + * Callback for deleting annotation + * @param {Object} data + */ +function deleteCallback(data, parentType) { + // remove overlay + $D.humanlayers.splice(data.index, 1); + // update layer manager + $UI.layersViewer.removeItemById(data.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(data.id, 'human', parentType); + + $CAMIC.viewer.omanager.removeOverlay(data.id); + if ($minorCAMIC && $minorCAMIC.viewer) { + $minorCAMIC.viewer.omanager.removeOverlay(data.id); + } + // update layers Viewer + // $UI.layersViewer.update(); + // close popup panel + $UI.annotPopup.close(); +} + +// for support QUIP2.0 Data model - delete callback +function deleteCallbackOld(data, parentType) { + const layer = $D.humanlayers[data.index]; + // for support QUIP2.0 + const idx = layer.data.findIndex((d) => d._id.$oid === data.oid); + if (idx == -1) return; + layer.data.splice(idx, 1); + + // update layer manager + $UI.layersViewer.removeItemById(data.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(data.id, 'human', parentType); + + // delete entire layer if there is no data. + if (layer.data.length == 0) { + $D.humanlayers.splice(data.index, 1); + $CAMIC.viewer.omanager.removeOverlay(data.id); + if ($minorCAMIC && $minorCAMIC.viewer) { + $minorCAMIC.viewer.omanager.removeOverlay(data.id); + } + } + + $CAMIC.viewer.omanager.updateView(); + // update layers Viewer + // $UI.layersViewer.update(); + // close popup panel + $UI.annotPopup.close(); +} + +function sortChange(sort) { + $CAMIC.layersManager.sort(sort); +} + +function resetCallback(data) { + if ($CAMIC.viewer.canvasDrawInstance._path_index === 0) return; + if (confirm(`Do You Want To Clear Markups?`)) { + $CAMIC.viewer.canvasDrawInstance.clear(); + } +} + +function convertToNormalized(points, size, viewer) { + const height = Math.round(viewer.imagingHelper.imgHeight); + const width = Math.round(viewer.imagingHelper.imgWidth); + // convert + const normalizedPoints = points.map((p) => [p[0] / width, p[1] / height]); + const normalizedSize = [size[0] / width, size[0] / height]; + + return { + points: normalizedPoints, + bound: getBounds(normalizedPoints), + size: normalizedSize, + }; +} + +function convertGeometries(features, data) { + const {points, bound, size} = convertToNormalized( + features, + data.size, + $CAMIC.viewer, + ); + + return { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + style: { + color: data.color, + lineJoin: 'round', + lineCap: 'round', + isFill: true, + }, + size: size, + }, + geometry: { + type: 'LineString', + coordinates: [points], + }, + bound: { + type: 'Polygon', + coordinates: [bound], + }, + }, + ], + }; +} + +/** + * Callback for saving annotations + * called from saveAnnotation + * @param {Object} 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(() => {}); +} + +function saveAnnotCallback() { + /* reset as default */ + // clear draw data and UI + $CAMIC.viewer.canvasDrawInstance.drawOff(); + $CAMIC.drawContextmenu.off(); + toggleOffDrawBtns(); + $CAMIC.viewer.canvasDrawInstance.clear(); + // uncheck pen draw icon and checkbox + // $UI.toolbar._subTools[1].querySelector('[type=checkbox]').checked = false; + // clear form + $UI.annotOptPanel.clear(); + + // close app side + $UI.toolbar._mainTools[0].querySelector('[type=checkbox]').checked = false; + $UI.appsSideMenu.close(); + // -- START QUIP550 -- // + // $UI.appsList.triggerContent('annotation','close'); + // -- END QUIP550 -- // + // open layer side + $UI.toolbar._mainTools[1].querySelector('[type=checkbox]').checked = true; + $UI.layersSideMenu.open(); + // $UI.layersViewer.update(); + $CAMIC.status = null; +} +function algoCallback(data) { + console.log(data); +} + +/** + * overlayer manager callback function for show or hide + * @param {Object} data + */ +const heatmapDefaultColor = '#1034a6'; + +const drawOptionRecord = {}; +function getDrawOption() { + drawOptionRecord.drawMode = $CAMIC.viewer.canvasDrawInstance.drawMode; + drawOptionRecord.color = $CAMIC.viewer.canvasDrawInstance.style.color; +} +function setDrawOption() { + $CAMIC.viewer.canvasDrawInstance.drawMode = drawOptionRecord.drawMode || 'free'; + $CAMIC.viewer.canvasDrawInstance.style.color = drawOptionRecord.color || '#7cfc00'; +} + +function toggleDownloadSelection(data) { + // const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + + // if(e.checked) { + // downloadSelectionOn(); + // } else { + // downloadSelectionOff(); + // } + if (!$CAMIC.viewer.canvasDrawInstance) { + console.warn('No Draw Tool'); + return; + } + if (data.checked) { + // trun off the main menu + $UI.layersSideMenu.close(); + if ($CAMIC.status == 'download_selection') { + downloadSelectionOn(); + return; + } + // turn off download SelectionOn + toolsOff(); + var checkAllToolsOff = setInterval(function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + downloadSelectionOn(); + } + }, 100); + // turn off magnifier + // magnifierOff(); + } else { + downloadSelectionOff(); + } +} + + +async function callback(data) { + const viewerName = this.toString(); + let camic = null; + switch (viewerName) { + case 'main': + camic = $CAMIC; + break; + case 'minor': + camic = $minorCAMIC; + break; + default: + break; + } + + // return; + data.forEach(function(d) { + const item = d.item; + if (item.typeName == 'ruler') { + if (!item.data) { + loadRulerById(camic, d, null); + } else { + if (!d.layer) { + const [xmin, ymin] = item.data.geometries.features[0].geometry.coordinates[0][0]; + const [xmax, ymax] = item.data.geometries.features[0].geometry.coordinates[0][2]; + // create lay and update view + d.layer = camic.viewer.measureInstance.addRuler({ + id: item.id, + mode: item.data.properties.mode, + rect: { + x: xmin, + y: ymin, + width: xmax-xmin, + height: ymax-ymin, + }, + innerHTML: item.data.properties.innerHTML, + isShow: d.isShow, + }); + } + if (d.isShow) d.layer.element.style.display =''; + else d.layer.element.style.display ='none'; + } + + return; + } + + if (item.typeName == 'segmentation') { + if (d.isShow) { + // add + camic.viewer.segment.addSegmentId(item.id); + } else { + // remove + camic.viewer.segment.removeSegmentId(item.id); + } + return; + } + if (item.typeName == 'heatmap') { + if ( + $D.heatMapData && + $D.heatMapData.provenance.analysis.execution_id == item.id && + camic.viewer.heatmap + ) { + // show or hide heatmap + if (d.isShow) { + camic.viewer.heatmap.on(); + } else { + camic.viewer.heatmap.off(); + } + } else if ( + $D.heatMapData && + $D.heatMapData.provenance.analysis.execution_id == item.id + ) { + const opt = { + opacity: 0.65, // inputs[2].value, + coverOpacity: 0.001, + data: $D.heatMapData.data, + // editedData:$D.editedDataClusters, + mode: 'binal', + size: $D.heatMapData.provenance.analysis.size, + fields: $D.heatMapData.provenance.analysis.fields, + color: heatmapDefaultColor, // inputs[3].value + }; + + if ($D.heatMapData.provenance.analysis.setting) { + opt.mode = $D.heatMapData.provenance.analysis.setting.mode; + if ($D.heatMapData.provenance.analysis.setting.field) { + opt.currentFieldName = + $D.heatMapData.provenance.analysis.setting.field; + } + } + camic.viewer.createHeatmap(opt); + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='binal') { + camic.viewer.heatmap.setColor($D.heatMapData.provenance.analysis.setting.colors[0]); + } + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='gradient') { + camic.viewer.heatmap.setColors($D.heatMapData.provenance.analysis.setting.colors); + } + } else { + Loading.open(document.body, 'Loading Heatmap Data...'); + // load heatmap + camic.store + .getHeatmap($D.params.slideId, item.id) + .then(function(data) { + if (Array.isArray(data) && data.length > 0) { + $D.heatMapData = data[0]; + const opt = { + opacity: 0.65, // inputs[2].value, + coverOpacity: 0.001, + data: $D.heatMapData.data, + mode: 'binal', + // editedData:$D.editedDataClusters, + size: $D.heatMapData.provenance.analysis.size, + fields: $D.heatMapData.provenance.analysis.fields, + color: heatmapDefaultColor, // inputs[3].value + }; + + if ($D.heatMapData.provenance.analysis.setting) { + opt.mode = $D.heatMapData.provenance.analysis.setting.mode; + if ($D.heatMapData.provenance.analysis.setting.field) { + opt.currentFieldName = + $D.heatMapData.provenance.analysis.setting.field; + } + } + camic.viewer.createHeatmap(opt); + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='binal') { + camic.viewer.heatmap.setColor($D.heatMapData.provenance.analysis.setting.colors[0]); + } + if ($D.heatMapData.provenance.analysis.setting && + $D.heatMapData.provenance.analysis.setting.mode=='gradient') { + camic.viewer.heatmap.setColors($D.heatMapData.provenance.analysis.setting.colors); + } + } + }) + .catch(function(error) { + // heatmap schema + console.error(error); + }) + .finally(function() { + Loading.close(); + if (!$D.heatMapData) { + $UI.message.addError('Loading Heatmap Data Is Error'); + } + }); + } + + // rest other check box + const cates = + viewerName == 'main' ? + $UI.layersViewer.setting.categoricalData : + $UI.layersViewerMinor.setting.categoricalData; + if (d.isShow) { + for (const key in cates) { + if (cates.hasOwnProperty(key)) { + cate = cates[key]; + if (cate.item.name == 'heatmap') { + cate.items.forEach((i) => { + if (d !== i && i.isShow) { + i.elt.querySelector('input[type=checkbox]').checked = false; + i.isShow = false; + } + }); + } + } + } + } + return; + } + + if (!item.data) { + // load annotation layer data + loadAnnotationById(camic, d, null); + } else { + if (!d.layer) d.layer = camic.viewer.omanager.addOverlay(item); + // remove popup if segment is hidden + if ($UI.annotPopup.data && !d.isShow && $UI.annotPopup.data.id===d.item.id) $UI.annotPopup.close(); + d.layer.isShow = d.isShow; + camic.viewer.omanager.updateView(); + } + }); +} +function minorCallback(data) { + console.log(data); +} + +function openMinorControlPanel() { + $UI.layersList.displayContent('left', true, 'head'); + $UI.layersList.triggerContent('left', 'close'); + $UI.layersList.displayContent('right', true); + $UI.layersList.triggerContent('right', 'open'); +} + +function closeMinorControlPanel() { + $UI.layersList.displayContent('left', false, 'head'); + $UI.layersList.triggerContent('left', 'open'); + $UI.layersList.displayContent('right', false); + $UI.layersList.triggerContent('right', 'close'); +} + +function loadRulerById(camic, rulerData, callback) { + const {item, elt} = rulerData; + $CAMIC.store + .getMarkByIds([item.id], $D.params.slideId, null, item.typeId) + .then((data) => { + // response error + if (data.error) { + const errorMessage = `${data.text}: ${data.url}`; + console.error(errorMessage); + $UI.message.addError(errorMessage, 4000); + // delete item form layview + removeElement($D.rulerlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'ruler'); + $UI.layersViewerMinor.removeItemById(item.id, 'ruler'); + return; + } + + // no data found + // if(data.length < 1){ + if (!data[0]) { + console.warn(`Ruler: ${item.name}(${item.id}) doesn't exist.`); + $UI.message.addWarning( + `Ruler: ${item.name}(${item.id}) doesn't exist.`, + 4000, + ); + // delete item form layview + removeElement($D.rulerlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'ruler'); + $UI.layersViewerMinor.removeItemById(item.id, 'ruler'); + return; + } + + item.data = data[0]; + if (data[0].provenance&& + data[0].provenance.analysis&& + data[0].provenance.analysis.coordinate&& + data[0].provenance.analysis.coordinate == 'image') { + data[0].geometries = ImageFeaturesToVieweportFeatures( + camic.viewer, + data[0].geometries, + ); + } + + item.data.properties.innerHTML = item.data.properties.innerHTML.split('<').join('<'); + item.data.properties.innerHTML = item.data.properties.innerHTML.split('>').join('>'); + item.data.properties.innerHTML = item.data.properties.innerHTML.split(' ').join(' '); + let [xmin, ymin] = item.data.geometries.features[0].geometry.coordinates[0][0]; + let [xmax, ymax] = item.data.geometries.features[0].geometry.coordinates[0][2]; + + // create lay and update view + rulerData.layer = camic.viewer.measureInstance.addRuler({ + id: item.id, + mode: item.data.properties.mode, + rect: { + x: xmin, + y: ymin, + width: xmax-xmin, + height: ymax-ymin, + }, + innerHTML: item.data.properties.innerHTML, + isShow: rulerData.isShow, + }); + }) + .catch((e) => { + console.error(e); + }) + .finally(() => { + Loading.close(); + }); +} +function loadAnnotationById(camic, layerData, parentType, callback) { + layerData.item.loading = true; + const item = layerData.item; + + Loading.open(document.body, 'Loading Layers...'); + + $CAMIC.store + .getMarkByIds([item.id], $D.params.slideId, null, item.typeId) + .then((data) => { + delete item.loading; + + // response error + if (data.error) { + const errorMessage = `${data.text}: ${data.url}`; + console.error(errorMessage); + $UI.message.addError(errorMessage, 4000); + // delete item form layview + removeElement($D.humanlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(item.id, 'human', parentType); + return; + } + + // no data found + // if(data.length < 1){ + if (!data[0]) { + console.warn(`Annotation: ${item.name}(${item.id}) doesn't exist.`); + $UI.message.addWarning( + `Annotation: ${item.name}(${item.id}) doesn't exist.`, + 4000, + ); + // delete item form layview + removeElement($D.humanlayers, item.id); + $UI.layersViewer.removeItemById(item.id, 'human', parentType); + $UI.layersViewerMinor.removeItemById(item.id, 'human', parentType); + return; + } + // for support quip 2.0 data model + if (data[0].geometry) { + // twist them + var image = camic.viewer.world.getItemAt(0); + var imgWidth = image.source.dimensions.x; + var imgHeight = image.source.dimensions.y; + item.data = data.map((d) => { + d.geometry.coordinates[0] = d.geometry.coordinates[0].map((point) => { + return [ + Math.round(point[0] * imgWidth), + Math.round(point[1] * imgHeight), + ]; + }); + d.properties = {}; + d.properties.style = { + color: '#000080', + lineCap: 'round', + lineJoin: 'round', + isFill: false, + }; + return { + _id: d._id, + provenance: d.provenance, + properties: d.properties, + geometry: d.geometry, + }; + }); + // if(item) data[0].isShow = item.isShow; + item.render = oldAnnoRender; + item.clickable = false; + item.hoverable = false; + } else { + if (data[0].provenance && + data[0].provenance.analysis&& + (data[0].provenance.analysis.coordinate == undefined|| + data[0].provenance.analysis.coordinate == 'normalized')) { + data[0].geometries = VieweportFeaturesToImageFeatures( + camic.viewer, + data[0].geometries, + ); + } + + if (data[0].provenance.analysis.isGrid) { + const width = $CAMIC.viewer.imagingHelper.imgWidth; + const height = $CAMIC.viewer.imagingHelper.imgHeight; + + const feature = data[0].geometries.features[0]; + const size = feature.properties.size; + // const points = feature.geometry.coordinates[0]; + // const bounds = feature.bound.coordinates[0]; + feature.properties.size = [ + Math.round(size[0] * width), + Math.round(size[1] * height), + ]; + // feature.geometry.coordinates[0] = points.map(p => [ + // Math.round(p[0] * width), + // Math.round(p[1] * height) + // ]); + // feature.bound.coordinates[0] = bounds.map(p => [ + // Math.round(p[0] * width), + // Math.round(p[1] * height) + // ]); + // item.data = data[0]; + // item.render = annoBrushRender; + } else { + // data[0].geometries = VieweportFeaturesToImageFeatures( + // camic.viewer, + // data[0].geometries + // ); + // item.data = data[0]; + // item.render = annoRender; + } + + item.data = data[0]; + item.render = annoRender; + } + + // create lay and update view + if (layerData.isShow) { + layerData.layer = camic.viewer.omanager.addOverlay(item); + camic.viewer.omanager.updateView(); + } + + if (callback) callback.call(layerData); + }) + .catch((e) => { + console.error(e); + }) + .finally(() => { + Loading.close(); + }); +} + +/** + * Deletes annotation from layer manager view + * @param {Object} layerData + */ +function removeCallback(layerData, parentType) { + item = layerData.item; + if (item.typeName == 'ruler') { + deleteRulerHandler(item.id); + return; + } + if (item.typeName !== 'human') return; + if (!item.data) { + // load layer data + loadAnnotationById($CAMIC, layerData, parentType, function() { + annoDelete({ + id: layerData.item.id, + oid: layerData.item.data._id.$oid, + annotation: layerData.item.data.properties.annotation, + }, parentType); + }); + } else { + annoDelete({ + id: layerData.item.id, + oid: layerData.item.data._id.$oid, + annotation: layerData.item.data.properties.annotation, + }, parentType); + } +} + +/** + * Callback for locating annotation on image + * @param {Object} layerData + */ +function locationCallback(layerData) { + let isImageViewer = true; + const item = layerData.item; + if ((item.typeName !== 'human'&&item.typeName !== 'ruler') || item.data == null) return; + if (item.typeName == 'ruler') isImageViewer = false; + + // loaction annotation 2.0 + if (Array.isArray(item.data)) { + let maxx = 0; + let maxy = 0; + let minx = Number.POSITIVE_INFINITY; + let miny = Number.POSITIVE_INFINITY; + item.data.forEach((d)=>{ + d.geometry.coordinates[0].forEach(([x, y])=>{ + maxx = Math.max(maxx, x); + maxy = Math.max(maxy, y); + minx = Math.min(minx, x); + miny = Math.min(miny, y); + }); + }); + const bound = [ + [minx, miny], + [maxx, miny], + [maxx, maxy], + [minx, maxy], + [minx, miny], + ]; + locateAnnotation(bound, isImageViewer); + return; + } + // locate annotation 3.0 + if (item.data.geometries.features[0].geometry.type == 'Point') { + const bound = item.data.geometries.features[0].bound.coordinates; + const center = $CAMIC.viewer.viewport.imageToViewportCoordinates( + bound[0], + bound[1], + ); + $CAMIC.viewer.viewport.panTo(center); + } else { + const bound = [...item.data.geometries.features[0].bound.coordinates[0]]; + if (item.data.provenance&&item.data.provenance.analysis&&item.data.provenance.analysis.isGrid) { + bound[2] = [bound[2][0] + + item.data.geometries.features[0].properties.size[0], bound[2][1] + + item.data.geometries.features[0].properties.size[1]]; + } + locateAnnotation(bound, isImageViewer); + } +} + +/** + * Locates annotation on the image + * called from locationCallback + * @param {Object} bound + */ +function locateAnnotation(bound, isImageViewer) { + // if(){ + + // } + const [minx, miny] = bound[0]; + const [maxx, maxy] = bound[2]; + const rectangle = isImageViewer? + $CAMIC.viewer.viewport.imageToViewportRectangle( + minx, + miny, + maxx - minx, + maxy - miny, + ):new OpenSeadragon.Rect( + minx, + miny, + maxx - minx, + maxy - miny, + ); + const center = rectangle.getCenter(); + $CAMIC.viewer.viewport.fitBounds(rectangle); + + setTimeout(() => { + const max = $CAMIC.viewer.cazoomctrlInstance.getMaxImageZoom($CAMIC.viewer); + const current = $CAMIC.viewer.viewport.viewportToImageZoom( + $CAMIC.viewer.viewport.getZoom(), + ); + if (current > max) { + $CAMIC.viewer.viewport.zoomTo( + $CAMIC.viewer.viewport.imageToViewportZoom(max), + center, + ); + } + }, 50); +} + +/* + collapsible list + 1. Annotation + 2. Analytics +*/ +function getCurrentItem(data) { + console.log(data); +} +// some fake events callback for demo + +function annotationSave() { + $UI.message.add('Annotation Saved'); +} +function algoRun() { + $UI.message.add('Algo is running...'); +} + +/** + * Saves annotation after drawing is stopped + */ +function saveAnnotation() { + annoCallback.call(null, { + id: + $UI.annotOptPanel.setting.formSchemas[$UI.annotOptPanel._select_.value] + .id, + data: $UI.annotOptPanel._form_.value, + }); +} + +function saveAnalytics() { + console.log('saveAnalytics'); +} +function startDrawing(e) { + // + if ( + $UI.toolbar.getSubTool('download_selection') && + $UI.toolbar.getSubTool('download_selection').querySelector('input[type=checkbox]') + .checked + ) { + console.log('start download_selection'); + return; + } + + if ( + $UI.toolbar.getSubTool('preset_label') && + $UI.toolbar.getSubTool('preset_label').querySelector('input[type=checkbox]') + .checked + ) { + // || + // $UI.toolbar.getSubTool("brush").querySelector("input[type=checkbox]") + // .checked + // ) + $CAMIC.viewer.canvasDrawInstance.stop = false; + } else { + $CAMIC.viewer.canvasDrawInstance.stop = !$UI.annotOptPanel._form_.isValid(); + } + // $CAMIC.viewer.canvasDrawInstance.stop = $UI.toolbar + // .getSubTool("preset_label") + // .querySelector("input[type=checkbox]").checked + // ? false + // : !$UI.annotOptPanel._form_.isValid(); + return; +} +function downloadSelection() { + const chks = $UI.downloadSelectionModal.body.querySelectorAll('input[type=checkbox][name=downloadSelection]:checked'); + if (!chks.length) { + alert('Please Check Annotation to Download.'); + return; + } + // + const execIds = []; + chks.forEach((chk)=>execIds.push(chk.dataset.id)); + // get annotation by + const bbox = $CAMIC.viewer.canvasDrawInstance._draws_data_[0].bound.coordinates[0]; + const height = $CAMIC.viewer.imagingHelper.imgHeight; + const width = $CAMIC.viewer.imagingHelper.imgWidth; + const x0 = bbox[0][0]/width; + const y0 = bbox[0][1]/height; + const x1 = bbox[2][0]/width; + const y1 = bbox[2][1]/height; + // + $CAMIC.store.getMarkByIds(execIds, $D.params.slideId, null, 'computer', null, x0, x1, y0, y1).then((data)=>{ + const element = document.createElement('a'); + const blob = new Blob([JSON.stringify(data)], {type: 'application/json'}); + const uri = URL.createObjectURL(blob); + element.setAttribute('href', uri); + element.setAttribute('download', `${$D.params.data.name}_${new Date().toISOString()}.json`); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + hideDownloadSelection(); + }); +} + +function showDownloadSelection() { + // get selection area + const height = $CAMIC.viewer.imagingHelper.imgHeight; + const width = $CAMIC.viewer.imagingHelper.imgWidth; + // get annotation by + const bbox = $CAMIC.viewer.canvasDrawInstance._draws_data_[0].bound.coordinates[0]; + const x0 = bbox[0][0]/width; + const y0 = bbox[0][1]/height; + const x1 = bbox[2][0]/width; + const y1 = bbox[2][1]/height; + // show up loading + $UI.downloadSelectionModal.body.innerHTML = 'Loading ...'; + $CAMIC.store.markSegmentationCount($D.params.slideId, x0, x1, y0, y1).then((data)=>{ + if (data&&Array.isArray(data)&&data.length) { + createSegmentTable(data); + } else { + $UI.downloadSelectionModal.body.innerHTML = 'No Data loaded ...'; + } + }); + $UI.downloadSelectionModal.open(); +} +function createSegmentTable(data) { + $UI.downloadSelectionModal.body.innerHTML = ''; + $UI.downloadSelectionModal.body.innerHTML = ` + + + + + + + + + ${data.sort((a, b)=> b._id - a._id).map((d)=>` + + + + `).join('')} + +
NameCountDownload
Segmentation / ${d._id}${d.count}
`; +} +function hideDownloadSelection() { + downloadSelectionOff(); + $UI.downloadSelectionModal.close(); +} + +function stopDrawing(e) { + if ( + $UI.toolbar.getSubTool('download_selection') && + $UI.toolbar.getSubTool('download_selection').querySelector('input[type=checkbox]') + .checked + ) { + // + showDownloadSelection(); + return; + } + + // preset label annotation + if ( + $UI.toolbar.getSubTool('preset_label') && + $UI.toolbar.getSubTool('preset_label').querySelector('input[type=checkbox]').checked + ) { + // save preset label + savePresetLabel(); + } else { + // annotation + const li = $UI.toolbar.getSubTool('annotation'); + const state = +li.querySelector('label').dataset.state; + if ( + state === 1 && + $CAMIC.viewer.canvasDrawInstance._draws_data_.length > 0 + ) { + $CAMIC.viewer.canvasDrawInstance.isOn = false; + } + } +} + +function openHeatmap() { + switch (ImgloaderMode) { + case 'iip': + // hosted + hostedHeatmap(); + break; + case 'imgbox': + // nano borb + imgboxHeatmap(); + break; + default: + // statements_def + break; + } +} +function hostedHeatmap() { + const slide = $D.params.slideId; + const slideName = $D.params.data.name; + $CAMIC.store + .findHeatmapType(slide) + // + .then(function(list) { + if (typeof list === 'undefined') { + list = []; + } + // get heatmap data + if (!list.length) { + // No heatmap data + $UI.message.addWarning(`info ${slideName} Has No Heatmap Data.`); + return; + } + createHeatMapList(list); + }) + // + .catch(function(error) { + console.error(error); + }) + // + .finally(function() { + if ($D.templates) { + // load UI + } else { + // set message + $UI.message.addError('HeatmapList'); + } + }); +} + +function imgboxHeatmap() { + let slide = $D.params.id; + slide = decodeURIComponent(slide); + // create file input + var element = document.createElement('input'); + element.setAttribute('type', 'file'); + element.style.display = 'none'; + document.body.appendChild(element); + + element.onchange = function(event) { + var input = event.target; + var reader = new FileReader(); + reader.onload = function() { + var text = reader.result; + try { + const data = JSON.parse(text); + + var valid = $VALIDATION.heatmap(data); + if (!valid) { + alert($VALIDATION.heatmap.errors); + return; + } + + $CAMIC.store.clearHeatmaps(); + + data.provenance.image.slide = slide; + const execId = data.provenance.analysis.execution_id; + Loading.open(document.body, 'Loading Heatmap...'); + $CAMIC.store + .addHeatmap(data) + .then((rs) => { + window.location.href = `../heatmap/heatmap.html${window.location.search}&execId=${execId}`; + }) + .catch((e) => { + $UI.message.addError(e); + console.error(e); + }) + .finally(() => { + Loading.close(); + }); + } catch (e) { + console.error(e); + Loading.close(); + } + }; + reader.readAsText(input.files[0]); + }; + element.click(); + document.body.removeChild(element); +} + +function createHeatMapList(list) { + empty($UI.modalbox.body); + $UI.modalbox.setHeaderText('HeatMap List'); + list.forEach((data) => { + const execId = data.provenance.analysis.execution_id; + const a = document.createElement('a'); + const params = getUrlVars(); + a.href = params.mode ? + `../heatmap/heatmap.html?slideId=${$D.params.slideId}&execId=${execId}&mode=pathdb` : + `../heatmap/heatmap.html?slideId=${$D.params.slideId}&execId=${execId}`; + a.textContent = execId; + $UI.modalbox.body.appendChild(a); + $UI.modalbox.body.appendChild(document.createElement('br')); + }); + $UI.modalbox.open(); +} + + +async function addPresetLabelsHandler(label) { + const rs = await $CAMIC.store.addPresetLabels(label).then((d)=>d.result); + + if (rs.ok&&rs.nModified > 0) { + $UI.labelsViewer.addLabel(label); + $UI.message.add(`Label "${label.type}" Has been Added`); + } else { + $UI.message.addError('Creating The New Label Has Failed'); + } + $UI.labelsViewer.__switch('view'); +} + +async function editPresetLabelsHandler(oldElt, newLabel) { + Loading.open($UI.labelsViewer.elt, 'Saving Label ...'); + + const rs = await $CAMIC.store.updatePresetLabels(oldElt.dataset.id, newLabel).then((d)=>d.result); + + if (rs.ok) { + // update + const rs1 = await $CAMIC.store.updateMarksLabel(newLabel.id, newLabel.type).then((d)=>d.result); + if (rs1.ok) { + // update UI + updateMarksLabel(newLabel.id, newLabel.type, $UI.layersViewer); + updateMarksLabel(newLabel.id, newLabel.type, $UI.layersViewerMinor); + + // + $UI.labelsViewer.setLabels(oldElt, newLabel); + $UI.message.add(`Label "${newLabel.type}" Has been Updated`); + if (oldElt.classList.contains('selected')) drawLabel({value: 'prelabels', checked: true}); + } else { + $UI.message.addError(`Updating The Marks' Label Has Failed`); + } + } else { + $UI.message.addError('Updating The Label Has Failed'); + } + Loading.close(); + $UI.labelsViewer.__switch('view'); +} +function updateMarksLabel(id, name, layersViewer) { + const cate = layersViewer.setting.categoricalData.human.items[id]; + if (cate) { + cate.item.name = name; + cate.elt.querySelector('label > div').textContent = name; + cate.elt.querySelector('input[type=checkbox]').dataset.name = name; + cate.items.forEach((e)=>{ + e.item.name = name; + e.item.label.name = name; + const newName = `${name}${e.elt.dataset.id}`; + e.elt.dataset.title = newName; + e.elt.querySelector('label > div').textContent = newName; + + if (e.item.data) { + e.item.data.provenance.analysis.name = name; + e.item.data.properties.annotations.name = name; + e.item.data.properties.annotations.notes = name; + } + }); + } +} +async function removePresetLabelsHandler(elt, label) { + const rs = await $CAMIC.store.removePresetLabels(label.id).then((d)=>d.result); + if (rs.ok&&rs.nModified > 0) { + $UI.message.add(`Label "${label.type}" Has been removed`); + $UI.labelsViewer.removeLabel(label.id); + } else { + $UI.message.addError(`Deleting The '${label.type}' Label Has Failed`); + } +} + +function selectedPresetLabelsHandler(label) { + drawLabel({value: 'prelabels', checked: true}); +} + +function drawLabel(e) { + if (!$CAMIC.viewer.canvasDrawInstance) { + alert('Draw Doesn\'t Initialize'); + return; + } + const labels = $UI.labelsViewer.getSelectedLabels(); + if (e.checked) { + if ($CAMIC.status == 'label') { + presetLabelOn.call(this, labels); + return; + } + // turn off annotation + toolsOff(); + + var checkAllToolsOff = setInterval( + function() { + if ($CAMIC && $CAMIC.status == null) { + // all tool has turn off + clearInterval(checkAllToolsOff); + presetLabelOn.call(this, labels); + } + }.bind(this), + 100, + ); + } else { + // off preset label + presetLabelOff(); + } +} + +function presetLabelOn(label) { + if (!$CAMIC.viewer.canvasDrawInstance) return; + // open labels viewer + mainMenuChange({apps: false, layers: false, labels: true}); + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + if (!label) { + $UI.message.addWarning('No Label Exist. Please Add A Label'); + $UI.toolbar.getSubTool('preset_label').querySelector('label').style.color = ''; + canvasDraw.clear(); + canvasDraw.drawOff(); + return; + } + + spen.menu(65, 0.2); + // open menu + canvasDraw.drawMode = label.mode; + if (label.mode == 'grid') { + canvasDraw.size = [parseInt(label.size), parseInt(label.size)]; + } + canvasDraw.style.color = label.color; + canvasDraw.drawOn(); + $CAMIC.status = 'label'; + $UI.toolbar.getSubTool('preset_label').querySelector('label').style.color = + label.color; +} + +function presetLabelOff() { + if (!$CAMIC.viewer.canvasDrawInstance) return; + const canvasDraw = $CAMIC.viewer.canvasDrawInstance; + spen.close(); + // close spen + if ( + canvasDraw._draws_data_.length && + confirm(`Do You Want To Save Annotation Label Before You Leave?`) + ) { + savePresetLabel(); + } else { + 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; + } +} + +function savePresetLabel() { + if ($CAMIC.viewer.canvasDrawInstance._path_index === 0) { + // toast + $UI.message.addWarning('info'+ + 'No Markup On Annotation. Try Holding And Dragging.', 4000); + return; + } + const data = $UI.labelsViewer.getSelectedLabels(); + if (!data) { + $UI.message.addWarning('No Label Selected. Please select One.', 4000); + return; + } + const execId = randomId(); + const labelId = data.id; + const labelName = data.type; + // const parent = data.type; + const noteData = { + id: execId, + labelId: labelId, + name: labelName, + notes: data.type, + }; + const feature = $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection() + .features[0]; + let annotJson; + if (feature.properties.size) { + // brush + const values = getGrids( + feature.geometry.coordinates[0], + feature.properties.size, + ); + const set = new Set(); + values.map((i) => i.toString()).forEach((v) => set.add(v)); + const points = Array.from(set).map((d) => d.split(',')); + annotJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: $D.params.slideId, + }, + analysis: { + source: 'human', + execution_id: execId, // randomId + name: labelName, // labelName + labelId: labelId, + type: 'label', + isGrid: true, + }, + }, + properties: { + annotations: noteData, + }, + geometries: convertGeometries(points, { + note: data.type, + size: feature.properties.size, + color: feature.properties.style.color, + }), + }; + } else { + // point / polygon / stringLine + annotJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: $D.params.slideId, + }, + analysis: { + source: 'human', + execution_id: execId, // randomId + name: labelName, // labelName + labelId: labelId, + type: 'label', + }, + }, + properties: { + annotations: noteData, + }, + geometries: ImageFeaturesToVieweportFeatures( + $CAMIC.viewer, + $CAMIC.viewer.canvasDrawInstance.getImageFeatureCollection(), + ), + }; + } + + $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; + } + const __data = data.ops[0]; + // create layer data + const newItem = { + id: execId, + name: noteData.name, + typeId: 'human', + typeName: 'human', + creator: getUserId(), + shape: annotJson.geometries.features[0].geometry.type, + isGrid: annotJson.provenance.analysis.isGrid? true: false, + label: { + id: annotJson.provenance.analysis.labelId, + name: annotJson.provenance.analysis.name, + }, + data: null, + }; + $D.humanlayers.push(newItem); + $UI.layersViewer.addHumanItem(newItem, 'human', labelId); + $UI.layersViewerMinor.addHumanItem( + newItem, + 'human', + labelId, + $minorCAMIC && $minorCAMIC.viewer ? true : false, + ); + + __data._id = {$oid: __data._id}; + addAnnotation( + execId, + __data, + 'human', + labelId, + ); + }) + .catch((e) => { + Loading.close(); + console.log('save failed', e); + }) + .finally(() => { + $UI.message.addSmall(`Added The '${noteData.name}' Annotation.`); + }); +} + +function addAnnotation(id, data, type, parent) { + const layerData = $UI.layersViewer.getDataItemById(id, type, parent); + const layerDataMinor = $UI.layersViewerMinor.getDataItemById(id, type, parent); + const item = layerData.item; + data.geometries = VieweportFeaturesToImageFeatures( + $CAMIC.viewer, + data.geometries, + ); + if (data.provenance.analysis.isGrid) { + const width = $CAMIC.viewer.imagingHelper.imgWidth; + const height = $CAMIC.viewer.imagingHelper.imgHeight; + + const feature = data.geometries.features[0]; + const size = feature.properties.size; + feature.properties.size = [ + Math.round(size[0] * width), + Math.round(size[1] * height), + ]; + } + item.data = data; + item.render = annoRender; + // create lay and update view + if (layerData.isShow) { + layerData.layer = $CAMIC.viewer.omanager.addOverlay(item); + $CAMIC.viewer.omanager.updateView(); + } + if ($minorCAMIC && $minorCAMIC.viewer && layerDataMinor.isShow) { + layerDataMinor.layer = $minorCAMIC.viewer.omanager.addOverlay(item); + $minorCAMIC.viewer.omanager.updateView(); + } + + $CAMIC.drawContextmenu.off(); + $CAMIC.viewer.canvasDrawInstance.clear(); + // close app side + $UI.toolbar._mainTools[0].querySelector('[type=checkbox]').checked = false; + $UI.appsSideMenu.close(); + // $UI.layersViewer.update(); + // $UI.layersViewerMinor.update(); +} + +function onDeleteRuler(ruler) { + const id = ruler.element.dataset.id; + deleteRulerHandler(id); +} + +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'); + }); +} + + +function onAddRuler(ruler) { + const {x, y, width, height} = ruler.getBounds($CAMIC.viewer.viewport); + const execId = 'ruler' + randomId(); + ruler.element.dataset.id = execId; + let innerHTML = ruler.element.innerHTML; + innerHTML = innerHTML.split('<').join('<'); + innerHTML = innerHTML.split('>').join('>'); + innerHTML = innerHTML.split(' ').join(' '); + let rulerJson = { + creator: getUserId(), + created_date: new Date(), + provenance: { + image: { + slide: $D.params.slideId, + }, + analysis: { + source: 'ruler', + execution_id: execId, + name: execId, + type: 'ruler', + }, + }, + properties: { + annotations: { + name: execId, + notes: 'ruler', + }, + mode: ruler.element.dataset.mode, + innerHTML: innerHTML, + }, + geometries: { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [ + x, + y, + ], + [ + x + width, + y, + ], + [ + x + width, + y + height, + ], + [ + x, + y + height, + ], + [ + x, + y, + ], + ], + ], + }, + bound: { + type: 'Polygon', + coordinates: [ + [ + [ + x, + y, + ], + [ + x + width, + y, + ], + [ + x + width, + y + height, + ], + [ + x, + y + height, + ], + [ + x, + y, + ], + ], + ], + }, + }, + ], + }, + + }; + + $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'); + }); +} + +async function rootCallback({root, parent, parentName, items}) { + // start a message + openLoadStatus(`${root==parent?root:`${root} - ${parentName}`}`); + // + const viewerName = this.toString(); + let camic = null; + switch (viewerName) { + case 'main': + camic = $CAMIC; + break; + case 'minor': + camic = $minorCAMIC; + break; + default: + break; + } + + parent = parent=='other'?null:parent; + // human other + var data; + // const ids = items.filter(d => d.item.id); + const ids = items.reduce(function(rs, d) { + if (!d.item.data) rs.push(d.item.id); + return rs; + }, []); + // loading + if (ids.length) { + // mult rulers + try { + data = await $CAMIC.store.getMarkByIds(ids, $D.params.slideId, (root=='human'&&parent!==null)?parentName:parent, root); + + if (data.error) { // error + closeLoadStatus(); + const errorMessage = `${data.text}: ${data.url}`; + console.error(errorMessage); + $UI.message.addError(errorMessage, 4000); + return; + } + // covert the ruler data + if (root == 'ruler') { + data.forEach((d) => { + if (d.provenance&& + d.provenance.analysis&& + d.provenance.analysis.coordinate&& + d.provenance.analysis.coordinate == 'image') { + d.geometries = ImageFeaturesToVieweportFeatures(camic.viewer, d.geometries); + } + + d.properties.innerHTML = d.properties.innerHTML.split('<').join('<'); + d.properties.innerHTML = d.properties.innerHTML.split('>').join('>'); + d.properties.innerHTML = d.properties.innerHTML.split(' ').join(' '); + }); + } else { + // covert the human data + data.forEach((d)=>{ + // for support quip 2.0 data model + if (d.geometry) { + var image = camic.viewer.world.getItemAt(0); + var imgWidth = image.source.dimensions.x; + var imgHeight = image.source.dimensions.y; + + // convert coordinates + d.geometry.coordinates[0] = d.geometry.coordinates[0].map((point) => { + return [ + Math.round(point[0] * imgWidth), + Math.round(point[1] * imgHeight), + ]; + }); + // set default color + d.properties = {}; + d.properties.style = { + color: '#000080', + lineCap: 'round', + lineJoin: 'round', + isFill: false, + }; + } else { + // conver coordinate if is `normalized` + if (d.provenance && + d.provenance.analysis&& + (d.provenance.analysis.coordinate == undefined|| + d.provenance.analysis.coordinate == 'normalized')) { + d.geometries = VieweportFeaturesToImageFeatures( camic.viewer, d.geometries ); + } + // if annotaion is brush then covert the size's coordinates + if (d.provenance.analysis.isGrid) { + const width = camic.viewer.imagingHelper.imgWidth; + const height = camic.viewer.imagingHelper.imgHeight; + + const feature = d.geometries.features[0]; + const size = feature.properties.size; + feature.properties.size = [ + Math.round(size[0] * width), + Math.round(size[1] * height), + ]; + } + } + }); + } + + // add to layer + } catch (error) { + closeLoadStatus(); + // finish loaded + console.error('loading human annotations error', error); + $UI.message.addError('loading human annotations error', 4000); + return; + } + } + + if (root == 'ruler') { + items.forEach((rulerData) => { + const item = rulerData.item; + // if no data then find and add to layer + if (!item.data) { + // TODO change to things else + const d = data.find( (d) => d.provenance.analysis.execution_id==item.id); + item.data = d; + let [xmin, ymin] = d.geometries.features[0].geometry.coordinates[0][0]; + let [xmax, ymax] = d.geometries.features[0].geometry.coordinates[0][2]; + // create lay and update view + rulerData.layer = camic.viewer.measureInstance.addRuler({ + id: item.id, + mode: d.properties.mode, + rect: { + x: xmin, + y: ymin, + width: xmax-xmin, + height: ymax-ymin, + }, + innerHTML: item.data.properties.innerHTML, + isShow: rulerData.isShow, + }); + } + if (rulerData.isShow) rulerData.layer.element.style.display =''; + else rulerData.layer.element.style.display ='none'; + }); + } + + if (root == 'human') { + items.forEach((HumanData) => { + const item = HumanData.item; + + if (!item.data) { + const d = data.filter( (d) => d.provenance.analysis.execution_id==item.id); + item.data = d[0].geometry?d:d[0]; + if (Array.isArray(item.data)&&item.data[0].geometry) { // 2.0 + item.render = oldAnnoRender; + item.clickable = false; + item.hoverable = false; + } else { + item.render = annoRender; + } + // HumanData.layer = camic.viewer.omanager.addOverlay(item); + } + if (!HumanData.layer) HumanData.layer = camic.viewer.omanager.addOverlay(item); + HumanData.layer.isShow = HumanData.isShow; + }); + + camic.viewer.omanager.updateView(); + } + closeLoadStatus(); +} + +/* Enhance Tool */ +function enhance(e) { + document.querySelector('[title="Enhance"]').previousSibling.checked = true; + if (!setEnhance) { + // $UI.message.add('infoOn Movement, enhance would be undone', 2000); + $CAMIC.viewer.addHandler('zoom', unenhance); + $CAMIC.viewer.addHandler('pan', unenhance); + setEnhance = true; + } + // Canvas information + const canvas = $CAMIC.viewer.canvas.firstChild; + const context = canvas.getContext('2d'); + let width = canvas.width; let height = canvas.height; + var img = context.getImageData(0, 0, width, height); + var data = img.data; + + if (e.status == 'Histogram Eq') { + context.putImageData(clahe(img, 64, 0.015), 0, 0); + } else if (e.status == 'Edge') { + context.putImageData(edgedetect_sobel(img, 80), 0, 0); + } else if (e.status == 'Sharpen') { + var filter = [[-1, -1, -1], [-1, 14, -1], [-1, -1, -1]]; + context.putImageData(applyfilter(img, filter), 0, 0); + } else if (e.status == 'Custom') { + var input = prompt('Enter the 2D Kernel: Eg : [[1, 0], [0, 1]]'); var f=0; + // JSON Parse test + try { + var filter = JSON.parse(input); var sz = filter[0].length; + } catch (e) { + alert('Invalid Kernel : ' + input); + return; + } + // 2D array check + for (var r=0; r + + + + + + + + + + + + + caMicroscope + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+ + +
+ + + +
+ + +
+ + +
+ + +
+ +
+ +
+ +
+ + +
+ + + + + + + + From 05c3f21af490bb7087061364d33c92acf833bc2f Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 25 Oct 2023 15:24:35 -0400 Subject: [PATCH 02/49] 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" > - + + + diff --git a/apps/table.html b/apps/table.html index 2648c8b9f..d059d5748 100644 --- a/apps/table.html +++ b/apps/table.html @@ -334,11 +334,12 @@