autoload.ts

//auto-initialization

import { GLViewer, createViewer } from "./GLViewer";
import { SurfaceType } from "./ProteinSurface4";
import { get, getbin, makeFunction, specStringToObject } from "./utilities";
import { CC } from "./colors";

export var autoinit = false;
export var processing_autoinit = false;

/**
 * Contains a dictionary of embedded viewers created from HTML elements
 * with a the viewer_3Dmoljs css class indexed by their id (or numerically
 * if they do not have an id).
*/
export var viewers: any = {};

//Create embedded viewer from HTML attributes if true
//viewer and callback are used by the testing harness
export function autoload(viewer?: any, callback?: (arg0: any) => void) {
    var i: string | number, dataname: string, type: string;
    if (document.querySelector(".viewer_3Dmoljs") != null)
        autoinit = true;

    if (autoinit) {
        processing_autoinit = true;
        viewer = (viewer != undefined) ? viewer : null;
        var nviewers = 0;
        document.querySelectorAll<HTMLInputElement>(".viewer_3Dmoljs").forEach(viewerdiv => {
            var datauri = [];
            var datatypes = [];
            var uri = '';

            if (viewerdiv.style.position == 'static') {
                //slight hack - canvas needs this element to be positioned
                viewerdiv.style.position = 'relative';
            }

            var UI:any = null;

            type = null;
            if (viewerdiv.dataset.pdb) {
                datauri.push("https://files.rcsb.org/view/" + viewerdiv.dataset.pdb + ".pdb");
                datatypes.push("pdb");
            } else if (viewerdiv.dataset.cid) {
                //this doesn't actually work since pubchem does have CORS enabled
                datatypes.push("sdf");
                datauri.push("https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/" + viewerdiv.dataset.cid +
                    "/SDF?record_type=3d");
            }
            else if (viewerdiv.dataset.href || viewerdiv.dataset.url) {
                if (viewerdiv.dataset.href)
                    uri = viewerdiv.dataset.href;
                else
                    uri = viewerdiv.dataset.url;
                datauri.push(uri);
                type = uri.substring(uri.lastIndexOf('.') + 1);
                if(type == 'gz') {
                    let pos = uri.substring(0,uri.lastIndexOf('.')).lastIndexOf('.');
                    type = uri.substring(pos+1);
                }
                datatypes.push(type);

                var molName = uri.substring(uri.lastIndexOf('/') + 1, uri.lastIndexOf('.'));
                if (molName == '/')
                    molName = uri.substring(uri.lastIndexOf('/') + 1);

                viewerdiv.dataset[datatypes[datatypes.length - 1]] = molName;
            }

            let divdata = viewerdiv.dataset;
            for (i in divdata) {
                if ((i.substring(0, 3) === "pdb" && (i !== "pdb"))) {
                    datauri.push("https://files.rcsb.org/view/" + divdata[i] + ".pdb");
                    datatypes.push('pdb');

                } else if (i.substring(0, 4) === "href" && (i !== "href")) {
                    uri = divdata[i];
                    datauri.push(uri);
                    datatypes.push(uri.substring(uri.lastIndexOf('.') + 1));
                } else if (i.substring(0, 3) === "cid" && (i !== "cid")) {
                    datauri.push("https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/" + divdata[i] + "/SDF?record_type=3d");
                    datatypes.push('sdf');
                }
            }
            var options = {};
            if (viewerdiv.dataset.options)
                options = specStringToObject(viewerdiv.dataset.options);

            //note that data tags must be lowercase
            var bgcolor = CC.color(viewerdiv.dataset.backgroundcolor);
            var bgalpha: string | number = viewerdiv.dataset.backgroundalpha;
            bgalpha = (bgalpha == undefined) ? 1.0 : parseFloat(bgalpha);
            var style = { line: {} };
            if (viewerdiv.dataset.style) style = specStringToObject(viewerdiv.dataset.style);
            var select = {};
            if (viewerdiv.dataset.select) select = specStringToObject(viewerdiv.dataset.select);
            var selectstylelist = [];
            var surfaces = [];
            var labels = [];
            var zoomto = {};
            var spin = null;
            var d = viewerdiv.dataset;

            //let users specify individual but matching select/style/surface tags, eg.
            //data-select1 data-style1
            var stylere = /style(.+)/;
            var surfre = /surface(.*)/;
            var reslabre = /labelres(.*)/;
            var keys = [];
            for (dataname in d) {
                if (Object.prototype.hasOwnProperty.call(d, dataname)) {
                    keys.push(dataname);
                }
            }
            keys.sort();
            for (i = 0; i < keys.length; i++) {
                dataname = keys[i];
                var m = stylere.exec(dataname);
                var selname: string, newsel: any, styleobj: any;
                if (m) {
                    selname = "select" + m[1];
                    newsel = specStringToObject(d[selname]);
                    styleobj = specStringToObject(d[dataname]);
                    selectstylelist.push([newsel, styleobj]);
                }
                m = surfre.exec(dataname);
                if (m) {
                    selname = "select" + m[1];
                    newsel = specStringToObject(d[selname]);
                    styleobj = specStringToObject(d[dataname]);
                    surfaces.push([newsel, styleobj]);
                }
                m = reslabre.exec(dataname);
                if (m) {
                    selname = "select" + m[1];
                    newsel = specStringToObject(d[selname]);
                    styleobj = specStringToObject(d[dataname]);
                    labels.push([newsel, styleobj]);
                }
                if (dataname == "zoomto") {
                    zoomto = specStringToObject(d[dataname]);
                }
                if (dataname == "spin") {
                    spin = specStringToObject(d[dataname]);
                }
            }

            //apply all the selections/styles parsed out above to the passed viewer
            var applyStyles = function (glviewer: GLViewer) {
                glviewer.setStyle(select, style);

                if (UI) {
                    UI.createSelectionAndStyle(select, style);
                }

                for (i = 0; i < selectstylelist.length; i++) {
                    let sel = selectstylelist[i][0] || {};
                    let sty = selectstylelist[i][1] || { "line": {} };
                    glviewer.setStyle(sel, sty);
                    if (UI) {
                        UI.createSelectionAndStyle(select, style);
                    }
                }
                for (i = 0; i < surfaces.length; i++) {
                    let sel = surfaces[i][0] || {};
                    let sty = surfaces[i][1] || {};
                    let viewer = glviewer;

                    if (UI) {
                        viewer.addSurface(SurfaceType.VDW, sty, sel, sel).then((surfid: any) => {
                            UI.loadSurface("VDW", sel, sty, surfid);
                        });
                    }
                    else {
                        glviewer.addSurface(SurfaceType.VDW, sty, sel, sel);
                    }

                }
                for (i = 0; i < labels.length; i++) {
                    let sel = labels[i][0] || {};
                    let sty = labels[i][1] || {};
                    glviewer.addResLabels(sel, sty);
                }

                glviewer.render();
                glviewer.zoomTo(zoomto);

                if (spin) {
                    glviewer.spin(spin.axis, spin.speed);
                }
            };

            let glviewer = viewer;
            try {
                var config: any = specStringToObject(viewerdiv.dataset.config) || {};
                if (config.backgroundColor === undefined) config.backgroundColor = bgcolor;
                if (config.backgroundAlpha === undefined) config.backgroundAlpha = bgalpha;
                if (glviewer == null) {
                    glviewer = viewers[viewerdiv.id || nviewers++] = createViewer(viewerdiv, config);
                } else {
                    glviewer.setBackgroundColor(bgcolor, bgalpha);
                    glviewer.setConfig(config);
                    if (UI) 
                        UI.initiateUI();
                }

                if(viewerdiv.dataset.ui && $3Dmol.StateManager) {
                    UI = new $3Dmol.StateManager(glviewer); // Creates the UI state management tool
                }
            } catch (error) {
                console.log(error);
                //for autoload, provide a useful error message
                viewerdiv.textContent = "WebGL appears to be disabled.";
            }

            if (datauri.length != 0) {
                //load multiple data elements in serial
                let i = 0;
                let process = ((viewerdiv, glviewer) => function (moldata: any) {
                    //add moldata to viewer and load next model
                    uri = datauri[i]; //this is where the moldata came from
                    var type = viewerdiv.dataset.type || viewerdiv.dataset.datatype || datatypes[i];
                    glviewer.addModel(moldata, type, options);
                    if (UI) {
                        var modelName = viewerdiv.dataset[datatypes[i]];
                        UI.setModelTitle(modelName);
                    }
                    i +=1;
                    if (i < datauri.length) {
                        get(datauri[i]).then(process);
                    }
                    else {
                        // or finalize if this is the last model
                        applyStyles(glviewer);
                        if (viewerdiv.dataset.callback) {
                            //evaluate javascript in the string, if it resolves to a function,
                            //call it with the viewer
                            let runres = makeFunction(viewerdiv.dataset.callback);
                            runres(glviewer);                            
                        }
                        processing_autoinit = false;
                        if (callback) callback(glviewer);
                    }
                })(viewerdiv,glviewer);

                if(type && type.endsWith('gz')) {
                    getbin(datauri[0]).then(process);
                } else {
                    get(datauri[0]).then(process);
                }
            }
            else {

                if (viewerdiv.dataset.element) {
                    var moldataid = "#" + viewerdiv.dataset.element;
                    var molelem = document.querySelector(moldataid);
                    var moldata = molelem ? molelem.textContent : "";
                    type = viewerdiv.dataset.type || viewerdiv.dataset.datatype;
                    glviewer.addModel(moldata, type, options);
                }

                applyStyles(glviewer);
                if (viewerdiv.dataset.callback) {
                    //evaluate javascript in the string, if it resolves to a function,
                    //call it with the viewer
                    let runres = makeFunction(viewerdiv.dataset.callback);
                    runres(glviewer);                    
                }
                processing_autoinit = false;
                if (callback)
                    callback(glviewer);
            }
        });
    }
};



document.onreadystatechange = () => {
    if (document.readyState === "complete") {
        autoload();
    }
};