<|start_filename|>tangos/web/static/stylesheet.css<|end_filename|>
td.smallinfo {
    font-size: 8px;
}

body {
    font-family: "Quicksand", "Helvetica Neue", Helvetica, Arial;
}

h1, h2, h3 {
    font-weight: bold;
}

h1 {
    font-size: 20px;
    color: #bb4400;
    background-color: #ffeecc;
    padding-top: 3px;
    padding-bottom: 2px;
    border-bottom: 1px solid #ccc;
    border-top: 1px solid #cce;
    padding-left: 10px;
}

h2 {
    font-size: 16px;
    text-transform: uppercase;
}

table {
    margin: 8px;
    border-collapse: collapse;
    border-bottom: 1px solid #ddd;
}

table#objects-container {
    margin: 0px;
    border: none;
}

table#objects-container tr td {
    vertical-align: top;
}

@keyframes rotation {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}

div.progress-spinner {
    display: inline-block;
    width: 1em;
    height: 1em;
    background-image: url('/static/tangos-50x50.png');
    background-size: cover;
    background-repeat: no-repeat;
    animation: rotation 2s infinite;
    animation-timing-function: linear;
}
tr.tangos-data:nth-child(odd) {
    background-color: #eeeef3;
}

tr.tangos-data:nth-child(even):hover {
    background-color: #eaeaea;
}

tr.tangos-data:nth-child(odd):hover {
    background-color: #dadae1;
}


td, th {
    padding: 2px;
    padding-left: 5px;
    padding-right: 5px;
}

td {
    font-family: 'Lucida Console', 'Lucida Sans Typewriter', monaco, 'Bitstream Vera Sans Mono', monospace;
    font-size: small;
    color: #222;
}

td.editable, th.editable {
    color: #bb4400;
}

a {
    color: #bb4400;
    text-decoration: none;
}

a:hover {
    color: #cc5511;
}



th {
    border-bottom: 1px solid #ddd;
    border-top: 1px solid #ddd;
}

#breadcrumbs-top {
    padding-top: 10px;
    padding-bottom: 4px;
    font-size: 12px;
}

#breadcrumbs-bottom {
    border-top: 1px solid #dde;
    clear: both;
    padding-top: 20px;
    font-size: 12px;
}

#tree-json {
    display: none;
}

#imgbox {
    width: 550px;
    height: 550px;
    overflow: scroll;
}

#imgbox::-webkit-scrollbar {
    -webkit-appearance: none;
}

#imgbox::-webkit-scrollbar:vertical {
    width: 11px;
}

#imgbox::-webkit-scrollbar:horizontal {
    height: 11px;
}

#imgbox::-webkit-scrollbar-thumb {
    border-radius: 8px;
    border: 2px solid white;
    background-color: rgba(0, 0, 0, .5);
}

#imgbox::-webkit-scrollbar-track {
    background-color: #fff;
    border-radius: 8px;
}

#imgbox {
    border: 1px solid #eef;
}

img.plotimg {
    float: right;
}

span.large {
    font-size:30px;
}

span.small_big {
    font-size:12px;
}

span.step_available a {
    
}

span.step_unavailable a {
    color: #f00;
}

#imgbox_container {
    float: right;
    width: 640px;
    height: 550px;
    position: absolute;
    right: 0px;
    top: 200px;
    padding: 10px;
}

#imgspace {
  
	
}

#navigation {
    width: 100%;
    background-color: rgba(223, 223, 223, 0.9);
    border-bottom: 1px solid gray;
    padding-left: 10px;
}

#flash-message{
    color: #500;
}

.hidden {
    display: none;
}

code  {
	font-size: 10px;
}

div.popup_parent {
    top:0;
    right:0;
    bottom:0;
    left:0;
    background-color: #fff;
    opacity: 0.5;
    z-index: 2;
    visibility: hidden;
    position: fixed;
}

div.popup_box {

    left: 20px;
    top: 100px;
    width: 300px;
    padding: 10px;
    font-size: 12px;
    background-color: #dde;
    z-index: 3;
    position: fixed;
    visibility: hidden;
}

pre {
    font-family: 'Lucida Console', 'Lucida Sans Typewriter', monaco, 'Bitstream Vera Sans Mono', monospace;
    white-space: pre-wrap;
    font-size: small;
}

.load_table_failed {
    color: gray;
}

.mono {
    font-family: 'Lucida Console', 'Lucida Sans Typewriter', monaco, 'Bitstream Vera Sans Mono', monospace;
    font-size: small;
    color: #222;
}

td.plotcontrols {
    text-align: center;
}


/* Merger tree */

g.node {
    font-family: Verdana, Helvetica;
    font-size: 12px;
    font-weight: bold;
}

circle.node-dot-standard {
    fill: #bb4400;
    stroke: #bb4400;
    stroke-width: 1px;
}

circle.node-dot-sub {
    fill: #bb4400;
    stroke: bb4400;
    stroke-width: 1px;
}

circle.node-dot-selected {
    fill: #e26000;
    stroke:black;
    stroke-width: 3px;
}

.phantom {
    stroke-dasharray: 2,2;
    fill-opacity: 0.5;
}

circle.node-dot-continuation {
    fill: white;
    stroke: #bbb;
    stroke-width: 1px;
}

path.link {
    fill: none;
    stroke: #e2e2e2;
}

text.info {
  font-family: Verdana, Helvetica;
  font-size: 12px;
  fill: gray;
}

rect.info {
  fill: white;
  stroke: gray;
}

table#halos-container tr td {
    vertical-align: top;
}

table.data-table tr th {
    min-width: 30px;
}

div.popup-controls {
    background-color: #eeeeee;
    font-size: large;
    padding: 3px;
    cursor: pointer;
}

div#properties-area {
    min-height: 700px;
    max-width: 600px;
}

ul.ui-autocomplete {
    max-height: 200px;
    overflow-y: scroll;
    overflow-x: hidden;
    font-family: 'Lucida Console', 'Lucida Sans Typewriter', monaco, 'Bitstream Vera Sans Mono', monospace;
    font-size: small;
}

<|start_filename|>tangos/web/static/keeponscreen.js<|end_filename|>


var scrollTop = {};

function initScrollOffsetData() {
    $(".keeponscreen").each(function() {
        if($(this).css('position')!='absolute') {
            // generate clone that keeps the space for this element
            var clone = $(this).clone();
            clone.removeClass("keeponscreen");
            clone.attr('id', clone.attr('id') + "-placeholder");
            clone.css('visibility', 'hidden');
            $(this).after(clone);
        }
        scrollTop[this.id]=this.getBoundingClientRect().top+window.scrollY;

    });
}

function updatePositionsAfterScroll() {
    var windowTop = $(window).scrollTop();
    var current=0;
    $(".keeponscreen").each(function() {
        if(windowTop<scrollTop[this.id]-current) {
            $(this).css({position:"absolute",
                         top: scrollTop[this.id]});
        } else {
            $(this).css({position:"fixed",
                         top: current});
        }

        clone = $("#"+this.id+"-placeholder");
        if (clone!=null)
            clone.css({height: this.getBoundingClientRect().height+10});

        current = this.getBoundingClientRect().bottom
    });

}

function setupScrollAdjustment() {
    initScrollOffsetData();
    $(window).scroll(updatePositionsAfterScroll);
    updatePositionsAfterScroll();

}

var hasInitialized;

$(function() {
    if(hasInitialized) return;
    hasInitialized=true;

    setupScrollAdjustment();

 });

<|start_filename|>tangos/web/static/dynamictable.js<|end_filename|>
var allEditables = [];

var addLabelText = "Add +";

Array.prototype.removeItem = function() {
    // from https://stackoverflow.com/questions/3954438/how-to-remove-item-from-array-by-value
    var what, a = arguments, L = a.length, ax;
    while (L && this.length) {
        what = a[--L];
        while ((ax = this.indexOf(what)) !== -1) {
            this.splice(ax, 1);
        }
    }
    return this;
};

jQuery.fn.positionDiv = function (divToPop) {
    var pos=$(this).offset();
    var h=$(this).height();
    var w=$(this).width();
    $(divToPop).css({ left: pos.left , top: pos.top + h + 5 , position: 'absolute', align: 'right' });

};

function popupControls(attachTo) {
    unpopupControls(attachTo);
    var id = attachTo.attr('id');
    var popupDiv = $("<div/>", {id: 'popup-controls-'+id, class: 'popup-controls'});
    popupDiv.html("&#x1F5D1;");
    popupDiv.appendTo('#content-container');
    attachTo.positionDiv(popupDiv);
    popupDiv.on('mousedown', function() {
        attachTo.trigger('deleteEditable');
    }).on( 'click', function() {});
}

function unpopupControls(attachedTo) {
    var id = attachedTo.attr('id');
    $('#popup-controls-'+id).remove();
}

$.fn.makeEditableTemplate = function(add, remove, update, editable_tag) {
    /* Mark a DOM element as a place to

    When a new editable element should be added, the add function is called.
    When an existing element should be updated, the update function is called.
    If the user asks for it to be removed, the remove function is called.

    Editable elements are automatically restored when a page is reloaded; this is
    accomplished by firing the add/update methods on the matching editable_tag.
     */


    var $this = $(this);

    allEditables.push($this);


    $this.data('editable_type_tag', editable_tag);
    $this.data('editable_add', add);
    $this.data('editable_remove', remove);
    $this.data('editable_update', update);
    
    $this.html(addLabelText);
    $this[0].contentEditable=true;
    enableAutocomplete($this);

    var savedContent;
    var column_id = $this.attr('id').substr(7);

    $this.css('cursor','pointer');

    $this.on({
        'keydown': function(e) {
            if(e.which=='13') {
                // enter
                $this.trigger('saveEditable');
                savedContent = undefined;
                $this.blur();
                return false;
            } else if(e.which=='27') {
                // escape
                $this.blur();
                $this.trigger('revertEditable');
                return false;
            }
        },
        'blur': function() {
            if(savedContent!==undefined) {
                $this.trigger('revertEditable');
            }
        },
        'focus': function() {
            $this.css('cursor','text');
            savedContent = $this.text();
            if(savedContent!==addLabelText)
                popupControls($this);
            if($this.text()===addLabelText) {
                putCursorAt(this, 0);
                $this.text("");
            }
        },
        'deleteEditable': function() {
            unpopupControls($this);
            remove(column_id, editable_tag);
            allEditables.removeItem($this);
        },
        'revertEditable': function() {
            unpopupControls($this);
            $this.css('cursor','pointer');
            $this.text(savedContent);
        },
        'saveEditable': function() {
            unpopupControls($this);
            $this.css('cursor','pointer');
            var content = $this.text();
            if(savedContent===addLabelText) {
                add(column_id, editable_tag);
            }

            if(content==="") {
                $this.trigger('deleteEditable');
            } else {
                update(content, column_id, editable_tag);
            }
        }
    });

    return this;
}

/*
function insertNewEditable(editable_name) {
    var previous_id = allEditables[allEditables.length-1].attr('id').substr(7);
    var id = defaultAddFn(previous_id);
    defaultUpdateFn(editable_name, previous_id);
}
*/


function uriEncodeQuery(name) {
    name = name.replace(/\//g,"_slash_")
    name = encodeURIComponent(name);
    return name;
}

function uriDecodeQuery(name) {
    name = name.replace(/_slash_/g,"/")
    name = decodeURIComponent(name);
    return name;
}

function getAllEditables() {
    var editables = [];
    forEach(allEditables, function(row) {
        var text = row.text();
        if(text!==addLabelText)
           editables.push(text);
    });
    return editables;
}

function persistAllEditables() {
    if(allEditables.length==0)
        return; // don't waste time – there's nothing to be stored from this page

    if(sessionStorage['editables']!==undefined)
        var oldEditables = JSON.parse(sessionStorage['editables']);
    else
        var oldEditables = {};

    var editables = {};

    forEach(allEditables, function(editable) {
        var type_tag = editable.data('editable_type_tag');
        if(!(type_tag in editables)) {
            editables[type_tag] = [];
        }
        var editables_of_type = editables[type_tag];
        if(editable.text()!="" && editable.text()!==addLabelText) {
            editables_of_type.push(editable.text());
        }
    });

    // for any editable categories that do not appear on this page, but that have
    // stored information, carry that stored information over.

    for(var key in oldEditables) {
        if(!(key in editables)) {
            editables[key] = oldEditables[key];
        }
    }

    sessionStorage['editables'] = JSON.stringify(editables);

}

function restoreAllEditables() {
    if(sessionStorage['editables']==undefined)
        return;

    var editables = JSON.parse(sessionStorage['editables'])

    forEach(allEditables, function(editable) {
        var type_tag = editable.data('editable_type_tag');
        if(type_tag in editables) {
            var editables_of_type = editables[type_tag];
            var old_column_id = editable.attr('id').substr(7);
            forEach(editables_of_type, function(name_to_add) {
                var add_fn = editable.data('editable_add');
                var update_fn = editable.data('editable_update');

                update_fn(name_to_add, old_column_id, type_tag);
                old_column_id = add_fn(old_column_id, type_tag);

            });

        }

    });

}

function getPlotControlElements(query, isScalar) {
    var uriQuery = uriEncodeQuery(query);
    if(isScalar)
        return '<input name="x" type="radio" onclick="resetRadio(\'justthis\');"  value="'+uriQuery+'"/>' +
               '<input name="y" type="radio" onclick="resetRadio(\'justthis\');" value="'+uriQuery+'"/>'
    else
        return '<input name="justthis" type="radio" value="'+uriQuery+'" onclick="resetRadio(\'x\'); resetRadio(\'y\');"/>'
}

function getFilterElements(query) {
    var uriQuery = uriEncodeQuery(query);
    return '<label>Filter <input name="filter-'+uriQuery+'" type="checkbox"/></label>'
}

function updatePlotControlElements(element, query, isScalar, isFilter, isArray, filterOnly) {
    var controls = {};
    $(element).find("input").each(function() {
       controls[this.name] = this.checked;
    });
    scalarControls = getPlotControlElements(query,true);
    arrayControls = getPlotControlElements(query,false);
    filterControls = getFilterElements(query)
    if(filterOnly) {
        scalarControls = "";
        arrayControls = "";
    }
    var buttonsHtml;
    if(isFilter) {
        hiddenHtml = arrayControls+scalarControls;
        visibleHtml = filterControls;
    } else if(isScalar) {
        hiddenHtml = arrayControls+filterControls;
        visibleHtml = scalarControls;
    } else if(isArray) {
        hiddenHtml = scalarControls+filterControls;
        visibleHtml = arrayControls;
    } else {
        hiddenHtml = scalarControls+filterControls+arrayControls;
        visibleHtml = "";
    }

    buttonsHtml = "<span class='hidden'>"+hiddenHtml+"</span>"+visibleHtml;

    $(element).html(buttonsHtml);
    $(element).find("input").each(function() {
        this.checked = controls[this.name];
    });
}

$(function() {
   restoreAllEditables();
});

$(window).on('beforeunload',function() {
    persistAllEditables();
});

<|start_filename|>tangos/web/static/timestepdata.js<|end_filename|>


function setupTimestepTables(timestep_url) {
    if(timestep_url!==window.timestep_url) {
        window.dataTables = {}
        window.timestep_url = timestep_url
    }
}

function getGatherUrl(typetag, miniLanguageQuery) {
    return timestep_url+"/gather/"+typetag+"/"+uriEncodeQuery(miniLanguageQuery)+".json";
}

function requestColumnData(editable_tag, miniLanguageQuery, callback) {
    if(window.dataTables === undefined ) {
        console.log("Attempt to request column data but the data tables have not yet been initialised")
        return undefined; // can't do anything useful
    }

    if(window.dataTables[editable_tag] === undefined) {
        window.dataTables[editable_tag] = {}
    }


    if(window.dataTables[editable_tag][miniLanguageQuery] === undefined) {
        var updateMarker = $("#update-marker-" + editable_tag);
        if(updateMarker!==undefined)
            updateMarker.html("<div class='progress-spinner'></div>");
        let reqs = updateMarker.data("pending-requests")
        if (reqs === undefined) reqs = 0;
        updateMarker.data("pending-requests", reqs + 1)
        console.log("Requesting "+miniLanguageQuery+" for "+editable_tag+"...");
        $.ajax({
            type: "GET",
            url: getGatherUrl(editable_tag, miniLanguageQuery),
            success: function (data) {
                if(updateMarker!==undefined) {
                    let reqs = updateMarker.data("pending-requests")
                    if (reqs === 1)
                        $("#update-marker-" + editable_tag).html('');
                    updateMarker.data("pending-requests", reqs - 1)
                }
                window.dataTables[editable_tag][miniLanguageQuery] = data;
                if(callback!==undefined)
                   callback(window.dataTables[editable_tag][miniLanguageQuery]);
            }
        });
    } else {
        if(callback!==undefined)
            callback(window.dataTables[editable_tag][miniLanguageQuery]);
    }

}

function getFilterArray(object_tag, get_id_from='th', callbackAfterFetch = undefined) {
    let columnsToFilterOn = [];
    let dataToFilterOn = [];

    const re = /filter-(.*)/;
    $('#properties_form_'+object_tag+' input[type="checkbox"]').each(function() {
        let $this = $(this);
        if($this.prop('checked') && $this.is(":visible")) {
            let name = $this.attr('name').match(re)[1];
            columnsToFilterOn.push(uriDecodeQuery(name));
        }
    });


    for(i=0; i<columnsToFilterOn.length; i++) {
        col = columnsToFilterOn[i];
        if(window.dataTables[object_tag][col]!==undefined) {
           dataToFilterOn.push(window.dataTables[object_tag][col].data_formatted);
        } else {
           if (callbackAfterFetch!==undefined) {
               requestColumnData(object_tag, col, callbackAfterFetch);
               return undefined;
           } // if no callback supplied, we carry on and get the best answer we can
        }
    }

    let nData = 0;

    try {
        nData = window.dataTables[object_tag]['halo_number()'].data_formatted.length;
    } catch(TypeError) {
        if (callbackAfterFetch!==undefined) {

           requestColumnData(object_tag, 'halo_number()', callbackAfterFetch);
       } // if no callback supplied, we carry on and get the best answer we can
        return undefined;
    }

    let filterArray = new Array(nData).fill(true);

    $.each(dataToFilterOn, function (j, c) {
        for(var i=0; i<nData; i++) {
            if (c[i] !== 'True') filterArray[i] = false;
        }
    });

    return filterArray;
}