/* globals structureData, createNGLViewer, $ */ $(window).on("load", function() { $('.viewport1').each(async function() { let proteinViewer = createNGLViewer(this, {structureData: structureData}); this.proteinViewer = proteinViewer; proteinViewer.addHandler('model-loaded', function(ev) { let infoElem = $('.structureInfo'); // Assembly let assemblyElem = infoElem.find('.assembly'); if (assemblyElem.length) { updateAssemblyList(ev.structure, proteinViewer.stage, assemblyElem); } // Title let data = proteinViewer.getStructureData(ev.structure); let elem = infoElem.find('.structureSelector[data-for="viewport1"] .structureTitle'); elem.html(data['title']); // Variants infoElem.find('.variants.clinical').html('Clinical (#='+data['variantCount']['c']['variants']+')'); infoElem.find('.variants.population').html('Population (#='+data['variantCount']['p']['variants']+')'); infoElem.find('.alphavariants.pathogenic').html('Pathogenic (#='+data['variantCount']['ac']['variants']+')'); infoElem.find('.alphavariants.benign').html('Benign (#='+data['variantCount']['ap']['variants']+')'); // Structure warning structureWarning(ev.structure); }); proteinViewer.addHandler('variant-changed', function(ev) { $('.structureInfo #variantLegend').html($(ev.legendData)); }); proteinViewer.addHandler('color-changed', function(ev) { $('.structureInfo #colorLegend').html(ev.legendData); }); proteinViewer.addHandler('highlight-changed', function(ev) { let slide = $('.color.highlight'); if (slide.hasClass('active')) { slide.trigger('toggle'); } else { slide.trigger('click'); } }); let transcript = $('.antigen_view .tab.selected').attr('name').replace('_', '-'); let structures = getTranscriptMappedStructures(transcript); await proteinViewer.run(structures[0]); }); $('.NGLFullscreen').on('click', function() { if (!document.fullscreenElement) { attemptNGLFullscreen($(this).closest('.NGLViewer').attr('id')); } else if (document.exitFullscreen) { document.exitFullscreen(); } }); addStructureFullscreenHandler(); const targetNode = document.getElementsByClassName("antigen_view")[0]; // Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = (mutationList) => { let c = debounceEvent(function(elem, mutation) {handleTranscriptDisplaySwitch(elem, mutation.target);}, 50); for (const mutation of mutationList) { if (mutation.type === "attributes" && mutation.target.classList.contains('tab_content') && !mutation.target.classList.contains('hidden')) { if (!mutation.target.querySelector('svg.transcript')) { let objectElem = mutation.target.querySelector('iframe.transcript_svg'); load_object_to_svg(objectElem); } c(undefined, mutation); } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); // Start observing the target node for configured mutations observer.observe(targetNode, config); $('.transcript_svg').each(function() { load_object_to_svg(this); }); $('.sequence_svg').each(function() { load_object_to_svg_seq(this); }); let proteinBrowser = $('#protein_browser'); proteinBrowser.on('click', '.transcriptView', function(e) { const transcriptElement = $(e.target).closest('.transcript').get(0); const name = transcriptElement.dataset.name.replace('-', '_'); $('.antigen_view .tab[name="'+name+'"] span').trigger('click'); }); proteinBrowser.on('transcriptChanged', function(e, data) { let transcript = data.transcript; let view = $('.transcriptView'); let transcripts = view.find('.transcript'); transcripts.removeClass('selected'); var tPos = transcripts.filter('.'+transcript).addClass('selected').position().top; var vPos = view.position().top; $('#transcriptName').html(transcript); $('#transcriptOffset').css('height', (tPos-vPos+5)+'px'); }); proteinBrowser.trigger('transcriptChanged', {transcript: $('.antigen_view .tab.selected').attr('name')}); // handleTranscriptDisplaySwitch(undefined, $('.antigen_view .tab_content:not(.hidden)').get(0)); $('.antigen_view').on('mouseover', 'svg.transcript.mapped rect.highlightable', function() { if (!$(this).attr('hovered')) { let t = $(this).attr('title'); $(this).attr('title', t+'<br><br>Click to highlight in 3D structure'); $(this).attr('hovered', true); } }); $('.antigen_view').on('click', 'svg.transcript.mapped rect.highlightable', function() { var aaStart = $(this).data('aaStart'); var aaStop = $(this).data('aaStop'); var name = $(this).data('name'); var color; if (!$(this).hasClass('antigen')) { color = parseInt($(this).attr('fill').slice(1), 16); } let isAntibody = false; if ($(this).data('type') === 'antibody') { isAntibody = true; } var transcript = $(this).closest('svg').data('transcript'); $('.viewport1, .viewport2').each(function() { let viewName = name; if (isAntibody) { let data = this.proteinViewer.getStructureData(this.proteinViewer.getLoadedStructure()); for (let ab in data.antigens) { if (data.antigens[ab].ab === name) { if (data.antigens[ab].identity_percent !== 100) { viewName = name + ' - match percentage: ' + data.antigens[ab].identity_percent + '%'; } break; } } } $(this).trigger('highlightSection', { 'transcript': transcript, 'name': viewName, 'startPos': aaStart, 'stopPos': aaStop, 'color': color }); }); }); $('body').on('click', 'div.colorLegendEntry.structureHL', function() { var key = $(this).data('key'); $('.viewport1, .viewport2').each(function() { $(this).trigger('highlightSection', { 'key': key, }); }); }); }); function protein_browser_tab_changed(elem, content) { const name = $(elem).closest('.tab').attr('name'); $('#protein_browser').trigger('transcriptChanged', {transcript: name}); } function load_object_to_svg(objectElem) { var svg = $(objectElem.contentDocument).find('svg'); svg.attr('id', $(objectElem).attr('id')+'_svg'); svg.attr('data-transcript', $(objectElem).attr('data-transcript')); let mapped = $(objectElem).hasClass('mapped'); if (mapped) { svg.addClass('mapped'); } svg.prependTo($(objectElem).parent()); if (svg.length) { $(objectElem).hide(); objectElem.parentNode.removeChild(objectElem); } if (svg.closest('.tab_content').is(':visible') && mapped) { $('#NGLViewer').show(); } } function load_object_to_svg_seq(objectElem) { var svg = $(objectElem.contentDocument).find('svg'); var parentWidth = $(objectElem).parent().width(); svg.attr('id', $(objectElem).attr('id')+'_svg').css({width:'100%'}); svg.prependTo($(objectElem).parent()); if (svg.length) { $(objectElem).hide(); objectElem.parentNode.removeChild(objectElem); } } // eslint-disable-next-line no-unused-vars async function handleTranscriptDisplaySwitch(elem, content) { // called when switching tab in protein browser var svg = $(content).find('svg'); if (svg.length) { let viewport = document.querySelector('.viewport1'); var msgElem = $('#structureMessage'); var viewerDiv = $('#NGLViewer'); let structures = getTranscriptMappedStructures(svg.data("transcript")); if (structures.length > 0) { viewport.proteinViewer.addOnceHandler('model-loaded', function() { $(viewport).trigger('resetHighlight'); viewerDiv.show(); msgElem.hide(); }); // await viewport.proteinViewer.run($('div.structureSelector select').val()); viewport.proteinViewer.loadmodel(structures[0]); } else { viewerDiv.hide(); if (msgElem.length) { msgElem.show(); } else { $('<p id="structureMessage">Transcript not mapped to available structure(s)</p>').appendTo(viewerDiv.parent()); } } } } function structureWarning(structure) { // FIXME: quickfix let svg = $('.antigen_view .tab_content:not(.hidden) svg'); // let structure = $('.viewport1')[0].viewer.getLoadedStructure(); let info = $('#structureTranscriptInfo'); let newData = ''; if (structure) { let structureData = document.querySelector('.viewport1').proteinViewer.getStructureData(structure); let s_length = structureData.structureLength; let t_length; if (structureData.transcriptMappings.hasOwnProperty(svg.data("transcript"))) { t_length = structureData.transcriptMappings[svg.data("transcript")].aa_seq_length; } if (t_length && s_length > t_length) { newData = '<p><span style="font-weight: bold">Notice:</span></p><br><div>Transcript is a subsequence of the displayed structure</div>'; } } if (newData !== info.html()) { info.html(newData); } } function getTranscriptMappedStructures(transcript) { let o = []; let structureData = document.querySelector('.viewport1').proteinViewer.getStructureDataEntries(); Object.keys(structureData).forEach(function(key) { let structure = structureData[key]; if (structure.transcriptMappings.hasOwnProperty(transcript)) { o.push(key); } }); return o; } function attemptNGLFullscreen(id) { const element = document.getElementById(id); if (!document.fullscreenElement) { element.dataset.originalWidth = element.clientWidth.toString(); element.dataset.originalHeight = element.clientHeight.toString(); element.querySelectorAll('canvas').forEach(function(e) { if (typeof e.dataset.originalWidth === "undefined") { e.dataset.originalWidth = e.width; e.dataset.originalHeight = e.height; } delete e.width; delete e.height; delete e.style.width; delete e.style.height; }); } if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen(); } else if (element.msRequestFullscreen) { element.msRequestFullscreen(); } else { throw new Error('Cannot open fullscreen'); } } function addStructureFullscreenHandler() { document.addEventListener('fullscreenchange', function() { if (!document.fullscreenElement) { document.getElementById('NGLViewer').querySelectorAll('canvas').forEach(function(e) { e.width = e.dataset.originalWidth; e.style.width = e.dataset.originalWidth+'px'; e.height = e.dataset.originalHeight; e.style.height = e.dataset.originalHeight+'px'; }); } document.querySelectorAll('.viewport1, .viewport2').forEach(function(e) { e.proteinViewer.stage.handleResize(); }); }); } function updateAssemblyList(structure, stage, assemblyElem) { assemblyElem = $(assemblyElem); assemblyElem.empty(); let components = stage.getComponentsByName(structure); let assemblies; if (components.list.length) { assemblies = components.list[0].structure.biomolDict; } assemblyElem.append(`<option value="">All</option>`); if (assemblies) { Object.keys(assemblies).forEach(function (k) { assemblyElem.append(`<option value="${k}">${k}</option>`); }); } } function debounceEvent(callback, delay = 250) { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { callback(...args); }, delay); }; }