UNPKG

ideogram

Version:

Chromosome visualization for the web

432 lines (360 loc) 11.6 kB
// import {getSettings, handleSettingsHeaderClick} from './settings-ui'; import version from '../version'; import {downloadPng} from '../lib'; const style = ` <style> #gear { position: absolute; right: 3px; top: 24px; z-index: 8001; cursor: pointer; height: 18px; width: 18px; } #tools { position: absolute; width: 120px; right: 27px; top: 16px; z-index: 8000; background: white; margin: 0; border: 1px solid #CCC; border-radius: 4px; box-shadow: -2px 4px 6px #CCC; } #tools ul { margin-block-start: 0; margin-block-end: 0; padding-inline-start: 0; } #tools li, #download li { padding: 2px 12px 2px 12px; cursor: pointer; } #tools li:hover, #tools li.active, #download li:hover { background: #DDD; } #tools li.ideo-disabled, #tools li.active.ideo-disabled, #download li.ideo-disabled { background: #FFF; color: #CCC; cursor: default; } #download { position: absolute; right: 3px; top: 16px; z-index: 8000; background: white; margin: 0; padding-inline-start: 0; } #settings { position: absolute; right: 3px; top: 16px; z-index: 8000; background: white; margin: 0; padding-inline-start: 0; } #about { position: absolute; right: 24px; top: -8px; z-index: 8000; background: white; width: 300px; border: 1px solid #CCC; padding: 10px; border-radius: 4px; box-shadow: -2px 4px 6px #CCC; cursor: default; } #close { float: right; border: 1px solid #DDD; border-radius: 4px; padding: 0 6px; background: #EEE; font-weight: bold; cursor: pointer; } #settings label { display: inline; text-decoration: underline; text-decoration-style: dotted; cursor: pointer; } #download { position: absolute; width: 120px; top: -2px; right: 120px; z-index: 8000; background: white; margin: 0; border: 1px solid #CCC; border-radius: 4px; box-shadow: -2px 4px 6px #CCC; } li { list-style-type: none; } #settings .no-underline { text-decoration: none; } #settings .setting { margin-right: 8px; } #settings input[type="checkbox"], #settings input[type="radio"] { position: relative; top: 2px; } .area-header { font-size: 16px; font-weight: bold; margin-bottom: 10px; clear: both; } .area-content { display: flex; flex-wrap: wrap; } .area-content > div { margin-right: 30px; margin-bottom: 15px; } .tab-panel input[type="number"] { width: 50px; } .tab-panel ul { width: 600px; list-style: none; border-bottom: 1px solid #CCC; box-sizing: border-box; margin-bottom: 0; padding-left: 0; } .tab-panel .nav:before, .tab-panel .nav:after { content: " "; display: table; clear: both; } .tab-panel li { float: left; margin-right: 2px; display: block; margin-bottom: -1px; } .tab-panel li > a { padding: 10px 15px; text-decoration: none; border-radius: 4px 4px 0 0; display: block; position: relative; } .tab-panel li.active > a { border: 1px solid #CCC; border-bottom: none; background-color: white; } .tab-panel .tab-content { width: 600px; } .tab-panel .tab-content > div { display: none; padding-top: 20px; clear: both; } .tab-panel .tab-content > div { padding: 20px 10px 5px 10px; } .tab-panel .tab-content > div.active { display: block; border: 1px solid #CCC; border-top: none; } </style>`; // eslint-disable-next-line max-len const gearIcon = '<svg viewBox="0 0 512 512"><path fill="#AAA" d="M444.788 291.1l42.616 24.599c4.867 2.809 7.126 8.618 5.459 13.985-11.07 35.642-29.97 67.842-54.689 94.586a12.016 12.016 0 0 1-14.832 2.254l-42.584-24.595a191.577 191.577 0 0 1-60.759 35.13v49.182a12.01 12.01 0 0 1-9.377 11.718c-34.956 7.85-72.499 8.256-109.219.007-5.49-1.233-9.403-6.096-9.403-11.723v-49.184a191.555 191.555 0 0 1-60.759-35.13l-42.584 24.595a12.016 12.016 0 0 1-14.832-2.254c-24.718-26.744-43.619-58.944-54.689-94.586-1.667-5.366.592-11.175 5.459-13.985L67.212 291.1a193.48 193.48 0 0 1 0-70.199l-42.616-24.599c-4.867-2.809-7.126-8.618-5.459-13.985 11.07-35.642 29.97-67.842 54.689-94.586a12.016 12.016 0 0 1 14.832-2.254l42.584 24.595a191.577 191.577 0 0 1 60.759-35.13V25.759a12.01 12.01 0 0 1 9.377-11.718c34.956-7.85 72.499-8.256 109.219-.007 5.49 1.233 9.403 6.096 9.403 11.723v49.184a191.555 191.555 0 0 1 60.759 35.13l42.584-24.595a12.016 12.016 0 0 1 14.832 2.254c24.718 26.744 43.619 58.944 54.689 94.586 1.667 5.366-.592 11.175-5.459 13.985L444.788 220.9a193.485 193.485 0 0 1 0 70.2zM336 256c0-44.112-35.888-80-80-80s-80 35.888-80 80 35.888 80 80 80 80-35.888 80-80z"/></svg>'; // Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com // License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) function deactivate(items) { items.forEach(item => {item.classList.remove('active');}); } function closeTools() { const toolHeaders = document.querySelectorAll('#tools > ul > li'); deactivate(toolHeaders); const itemsToClose = document.querySelectorAll('.ideo-modal, .ideo-tool-panel'); itemsToClose.forEach(item => {item.remove();}); document.querySelector('#tools').style.display = 'none'; } /** * As needed, hide tool panels that are triggered by hovering */ function handleHideForHoverables(trigger, tool, toolHeader, toolHeaders) { if (trigger === 'mouseenter') { // Hide panel when hover leaves tool header, if new target element // is part of the tools UI (and not the panel itself) toolHeader.addEventListener('mouseleave', event => { const toElement = event.toElement; const toId = toElement.id; const panelElement = document.querySelector('.ideo-tool-panel'); const toolsElement = document.querySelector('#tools'); if ( toolsElement.contains(toElement) && panelElement && !panelElement.contains(toElement) && toId !== tool ) { deactivate(toolHeaders); panelElement.remove(); } }); } } /** Determine action that should trigger a tool panel to display */ function getTrigger(toolHeader) { const shouldHover = Array.from(toolHeader.classList).includes('ideo-tool-hover'); const trigger = shouldHover ? 'mouseenter' : 'click'; return trigger; } /** Shows clicked tool as active, displays resulting panel */ function handleToolClick(ideo) { const toolHeaders = document.querySelectorAll('#tools > ul > li'); toolHeaders.forEach(toolHeader => { const trigger = getTrigger(toolHeader); toolHeader.addEventListener(trigger, event => { // Show only clicked tool header as active deactivate(toolHeaders); toolHeader.classList += ' active'; const tool = toolHeader.id.split('-')[0]; const panel = getPanel(tool, ideo); if (trigger === 'mouseenter') { toolHeader.insertAdjacentHTML('beforeend', panel); handleHideForHoverables(trigger, tool, toolHeader, toolHeaders); if (tool === 'download') { document.querySelector('#download-image') .addEventListener('click', event => { closeTools(); downloadPng(ideo); }); document.querySelector('#download-annots') .addEventListener('click', event => { const element = document.querySelector('#download-annots'); const classes = Array.from(element.classList); if (classes.includes('ideo-disabled') === false) { closeTools(); ideo.downloadAnnotations(); } }); } } else { document.querySelector('#gear').insertAdjacentHTML('beforeend', panel); } }); }); // Upon clicking "close" (x), remove tools UI document.querySelectorAll('#close').forEach(closeButton => { closeButton.addEventListener('click', () => {closeTools();}); }); } function handleGearClick(ideo) { document.querySelector('#gear') .addEventListener('click', event => { var options = document.querySelector('#tools'); if (options.style.display === 'none') { options.style.display = ''; hideOnClickOutside(); } else { options.style.display = 'none'; closeTools(); } }); handleToolClick(ideo); // handleSettingsHeaderClick(ideo); } function showGearOnIdeogramHover(ideo) { const container = document.querySelector(ideo.selector); const gear = document.querySelector('#gear'); const panel = document.querySelector('#tools'); container.addEventListener('mouseover', () => gear.style.display = ''); container.addEventListener('mouseout', () => { // Hide gear only if panel is not shown if (panel.style.display === 'none') { gear.style.display = 'none'; } }); gear.addEventListener('mouseover', () => gear.style.display = ''); } function getPanel(tool, ideo) { var panel; // if (tool === 'settings') panel = getSettings(); if (tool === 'download') panel = getDownload(ideo); if (tool === 'about') panel = getAbout(); return panel.trim(); } function getDownload(ideo) { const numAnnots = document.querySelectorAll('.annot').length; const annotsClass = (numAnnots > 0) ? '' : 'ideo-disabled'; return ` <div id="download" class="ideo-tool-panel"> <li id="download-image">Image</li> <li id="download-annots" class="${annotsClass}">Annotations</li> </div> `; } function getAbout() { const ideogramLink = ` <a href="https://github.com/eweitz/ideogram" target="_blank" rel="noopener"> Ideogram.js</a>`; const closeButton = '<span id="close">x</span>'; return ` <div id="about" class="ideo-modal"> ${ideogramLink}, version ${version} ${closeButton}<br/> <i>Chromosome visualization for the web</i> </div>`; } function hideOnClickOutside(selector) { const elements = document.querySelectorAll('#gear, #tools'); const outsideClickListener = event => { let clickedOutsideCount = 0; elements.forEach((element) => { if (!element.contains(event.target)) { clickedOutsideCount += 1; } }); if (clickedOutsideCount === elements.length) { closeTools(); removeClickListener(); } }; const removeClickListener = () => { document.removeEventListener('click', outsideClickListener); }; document.addEventListener('click', outsideClickListener); } function initTools(ideo) { const triangle = '<span style="float: right">&blacktriangleright;</span>'; const toolsHtml = ` ${style} <div id="gear" style="display: none">${gearIcon}</div> <div id="tools" style="display: none"> <ul> <li id="download-tool" class="ideo-tool-hover">Download ${triangle}</li> <li id="about-tool">About</li> </ul> </div>`; document.querySelector(ideo.selector) .insertAdjacentHTML('beforebegin', toolsHtml); handleGearClick(ideo); showGearOnIdeogramHover(ideo); } export {initTools};