<commit_before>/*
Copyright (C) 2012, 2013 Alexander Murashkin, Neil Redman <http://gsd.uwaterloo.ca>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
function Input(host, settings)
{
    this.id = "mdInput";
    this.settings = settings;
    this.title = this.settings.title;

    this.requestTimeout = 60000; // what is the timeout for response after sending a file
    this.pollingTimeout = 60000;  // what is the timeout when polling
    this.pollingDelay = 700;    // how often to send requests (poll) for updates
    this.pollingTimeoutObject = null;
    this.toCancel = false;

    this.width = this.settings.layout.width;
    this.height = this.settings.layout.height;
    this.posx = this.settings.layout.posx;
    this.posy = this.settings.layout.posy;

    this.host = host;
    this.serverAction = "/upload";
    this.serverOptimizeAction = "/optimize";

    this.dataFileChosen = false;

    this.editor = null;
    this.editorWidth = this.width - 5;
    this.editorHeight = this.height - 83;

    this.resize = this.onResize.bind(this);

    if (this.settings.optimization_backend)
    {
        this.loadCounter = 2; // because 2 xmls to be loaded
    }
    else
        this.loadCounter = 1;
}

//Input.method("recalculateEditorSize", function()
//{
//    this.editorWidth = this.window.width - 5;
//    this.editorHeight = this.window.height - 83;
//});

Input.method("onInitRendered", function()
{
    this.optimizeFlag = 1;
    this.addInstancesFlag = 1;
    this.previousData = null;
    this.toCancel = false;

    $("#submitFile").click(this.submitFileCall.bind(this));
    $("#submitExample").click(this.submitExampleCall.bind(this));
    $("#submitText").click(this.submitTextCall.bind(this));
    $("#submitExample").attr("disabled", "disabled");

    $("#submitFile").attr("disabled", "disabled");

    $("#myform [type='file']").change(this.inputChange.bind(this));
    $("#exampleURL").change(this.exampleChange.bind(this));
    $("#loadExampleInEditor").change(this.exampleChange.bind(this));
//    $("#saveSourceButton").click(this.saveSourceCall.bind(this));

    var options = new Object();
    options.beforeSubmit = this.beginQuery.bind(this);
    options.success = this.fileSent.bind(this);
    options.error = this.handleError.bind(this);
    options.timeout = this.requestTimeout;

    $('#myform').ajaxForm(options);

//    var options = new Object();
//    options.error = this.handleError.bind(this);
//    options.timeout = this.requestTimeout;

//    $('#optimizeForm').ajaxForm(options);

//    var optionsForFile = new Object();
//    optionsForFile.success = this.saveSourceSuccess.bind(this);
//    optionsForFile.error = this.handleError.bind(this);
//    optionsForFile.timeout = this.requestTimeout;
//    $('#saveSourceForm').ajaxForm(optionsForFile);

    this.editor = ace.edit("clafer_editor");
    this.editor.setTheme("ace/theme/eclipse");
    var ClaferMode = require("ace/mode/clafer").Mode;
    this.editor.getSession().setMode(new ClaferMode());
    this.editor.setShowPrintMargin(false);

    // $('#myform').submit(); MOVED TO another location
});

/*
 * Cancel request
 */

Input.method("cancelCall", function()
{
    $("#cancel").hide();
    $("#status_label").html("Cancelling...");
    this.toCancel = true;
});

/*
 * Shows uploader and hides the form
*/
Input.method("beginQuery", function(formData, jqForm, options) {

    if (!this.settings.onBeginQuery(this))
    {
        return false;
    }

    $("#load_area #myform").hide();
    $("#load_area").append('
Loading and processing...<button id="cancel">Cancel</button>
');
    $("#cancel").click(this.cancelCall.bind(this));

    return true;
});

// post-submit callback
Input.method("endQuery", function()  {
    $("#preloader").remove();
    $("#load_area #myform").show();

    $("#claferFileURL").val(""); // empty the URL

    return true;
});

Input.method("onPoll", function(responseObject)
{
    if (!responseObject)
    {
        this.handleError(null, "empty_argument", null);
        return;
    }

    this.settings.onPoll(this, responseObject);

    if (responseObject.message == "Working")
    {
        this.pollingTimeoutObject = setTimeout(this.poll.bind(this), this.pollingDelay);
    }
    else // finished
    {
        this.settings.onCompleted(this, responseObject);
        this.endQuery();
    }
});

Input.method("poll", function()
{
    var options = new Object();
    options.url = "/poll";
    options.type = "post";
    options.timeout = this.pollingTimeout;
    if (!this.toCancel)
        options.data = {windowKey: this.host.key, command: "ping"};
    else
        options.data = {windowKey: this.host.key, command: "cancel"};

    options.success = this.onPoll.bind(this);
    options.error = this.handleError.bind(this);

    $.ajax(options);
});

Input.method("fileSent", function(responseText, statusText, xhr, $form)  {
    this.toCancel = false;

    if (responseText == "error")
    {
        this.handleError(null, "compile_error", null);
        return;
    }

    if (responseText != "no clafer file submitted")
    {
        this.settings.onFileSent(this);
        this.pollingTimeoutObject = setTimeout(this.poll.bind(this), this.pollingDelay);
    }
    else
    {
        this.endQuery(); // else enable the form anyways
//        this.setClaferModelHTML(this.host.findModule("mdCompiledFormats").lastModel);
    }
});

Input.method("handleError", function(response, statusText, xhr)  {
    clearTimeout(this.pollingTimeoutObject);
    var er = document.getElementById("error_overlay");
    er.style.display = "block";
    var caption = this.settings.onError(this, statusText, response, xhr);

    document.getElementById("error_report").innerHTML = ('Close Message
' + caption + "
");
    document.getElementById("close_error").onclick = function(){
        document.getElementById("error_overlay").style.display = "none";
    };
    this.endQuery();

});

Input.method("onSubmit", function(){
    if (this.pollingTimeoutObject)
        clearTimeout(this.pollingTimeoutObject);
});

Input.method("submitFileCall", function(){

    $("#exampleURL").val(null);
    $("#exampleFlag").val("0");
    this.onSubmit();
});

Input.method("submitExampleCall", function(){
    $("#exampleFlag").val("1");
    this.onSubmit();
});

Input.method("submitTextCall", function(){
    $("#claferText").val(this.editor.getValue());
    $("#exampleFlag").val("2");
    this.onSubmit();
});

Input.method("exampleChange", function(){
    if ($("#exampleURL").val())
    {
        $("#submitExample").removeAttr("disabled");
    }
    else
    {
        $("#submitExample").attr("disabled", "disabled");
    }
});

Input.method("inputChange", function(){
    var filename = $("#myform [type='file']").val();

    if (filename)
    {
        if (filename.substring(filename.length-4) == ".cfr"){
            $("#submitFile").removeAttr("disabled");
            $("#submitFile").val(this.settings.button_file_caption);
        }
        else{ // unknown file
            $("#submitFile").val("Unknown");
            $("#submitFile").attr("disabled", "disabled");
        }
    }
    else{ // no file
        $("#submitFile").attr("disabled", "disabled");
        $("#submitFile").val(this.settings.button_file_caption);
    }

});

Input.method("getInitContent", function()
{
    result = '
';
    result += '<form id="myform" action="' + this.serverAction + '" method="post" enctype="multipart/form-data" style="display: block; height:100%">';

    result += '<input type="hidden" name="claferFileURL" id="claferFileURL" value="' + this.host.claferFileURL + '">';
    result += '<input type="hidden" name="exampleFlag" id="exampleFlag" value="0">';
    result += '<input type="hidden" id="windowKey" name="windowKey" value="' + this.host.key + '">';
    result += '<input id="claferText" name="claferText" type="hidden"/>';

    result += '
';
    result += '
';
    result += '<input type="file" size="20" name="claferFile" id="claferFile" title="If you want to upload your clafer file, select one here "/>	';
    result += '<input id="submitFile" type="submit" value="' + this.settings.button_file_caption + '" title="' + this.settings.button_file_tooltip + '"/>	';
    result += '<input id="loadExampleInEditor" type="checkbox" name="loadExampleInEditor" value="unchecked" title="If checked, the editor window below will be loaded with a file or an example submitted">Load into editor</input>	';
    result += '

';
    result += '<select id="exampleURL" style="width:220px" name="exampleURL" title="If you want, you can choose to compile an example clafer model from the list">';

    result += '</select>	';
    result += '<input id="submitExample" type="submit" value="' + this.settings.button_example_caption + '" title="' + this.settings.button_example_tooltip + '"></input>	';

    result += 'Scopes: <select id="ss" name="ss" title="Choose a scope computing strategy. Scopes are used for instantiation using bounded model checking">';

    result += '<option value="none" title="Disable scope computing strategy. All scopes are to be set to 1">Disabled</option>';
    result += '<option value="simple" selected="selected" title="Fast computation. Scopes are not precise, but this strategy works in most cases">Fast</option>';
    result += '<option value="full" title="Full computation. This method is very slow, but for small models works relatively fast">Full</option>';

    result += '</select>	';

    result += '

';
    result += '';
    result += 'Or enter your model:	';
    result += '<input id="submitText" type="submit" value="' + this.settings.button_editor_caption + '" title="' + this.settings.button_editor_tooltip + '"/>	';

    result += 'Flags: <input id="args" type="text" style="width:90px;" name="args" value="-k" title="You can specify any additional compilation flags supported by the compiler"></input>	';

    var padding = "";
    if (this.settings.optimization_backend)
    {
        padding = 'padding-bottom:35px;';
    }

    result += '

';

    result += '
	';

    result += '

';

    var context = this;

    if (this.settings.optimization_backend)
    {
        result += '
';
        result += '

';

        result += 'Optimization backend: <select id="optimizationBackend" style="width:180px" name="optimizationBackend" title=""></select>';

        result += '<input id="useCache" type="checkbox" name="useCache" value="checked">Use Cache</input>';

        result += '
';

        $.getJSON('/Backends/backends.json',
            function(data)
            {
                var backends = data.backends;
                var options = "";

                var display = "block";

                for (var i = 0; i < backends.length; i++)
                {
                    options += '<option value="' + backends[i].id + '" title="' + backends[i].tooltip + '">' + backends[i].label + '</option>';
                }

                $("#optimizationBackend").html(options);

                context.partLoaded();
            }
        ).error(function()
            {
                var options = '<option value="">(Could not load instance generators)</option>';
                $("#optimizationBackend").html(options);
                context.partLoaded();

            });
    }

    result += '</form>';

//    result += '<form id="saveSourceForm" action="/savesource" method="post" enctype="multipart/form-data">';
//    result += '<input type="hidden" name="windowKey" value="' + this.host.key + '"/>';
//    result += '<input type="hidden" name="saveSourceField" id="saveSourceField" value=""></form>';

    var context = this;

    $.getJSON('/Examples/examples.json',
        function(data)
        {
            var examples = data.examples;
            var options = "";

            for (var i = 0; i < examples.length; i++)
            {
                var optionClass = 'normal_option';

                if (i == 0)
                    optionClass = 'first_option';

                options += '<option class="' + optionClass + '" value="' + examples[i].url + '">' + examples[i].label + '</option>';
            }

            $("#exampleURL").html(options);

            context.partLoaded();

        }
    ).error(function()
        {
            var optionClass = 'first_option';
            var options = '<option class="' + optionClass + '" value="">Or Choose Example (Could not load examples)</option>';
            $("#exampleURL").html(options);
            context.partLoaded();

        });

    return result;

});

Input.method("onResize", function()
{
    this.editor.resize();
});

Input.method("partLoaded", function()
{
    this.loadCounter = this.loadCounter - 1;

    if (this.loadCounter == 0)
    {
       this.host.loaded(this); // notify about getting fully loaded
    }
});

function unescapeJSON(escaped)
{
    return escaped
        .replaceAll('\\\\', '\\')
        .replaceAll('\\"', '"')
        .replaceAll('\\/', '/')
        .replaceAll('\\b', '\b')
        .replaceAll('\\f', '\f')
        .replaceAll('\\n', '\n')
        .replaceAll('\\r', '\r')
        .replaceAll('\\t', '\t');
}
<commit_msg>Added Input settings and tags<commit_after>/*
Copyright (C) 2012, 2013 Alexander Murashkin, Neil Redman <http://gsd.uwaterloo.ca>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
function Input(host, settings)
{
    this.id = "mdInput";
    this.settings = settings;
    this.title = this.settings.title;

    this.requestTimeout = 60000; // what is the timeout for response after sending a file
    this.pollingTimeout = 60000;  // what is the timeout when polling
    this.pollingDelay = 700;    // how often to send requests (poll) for updates
    this.pollingTimeoutObject = null;
    this.toCancel = false;

    this.width = this.settings.layout.width;
    this.height = this.settings.layout.height;
    this.posx = this.settings.layout.posx;
    this.posy = this.settings.layout.posy;

    this.host = host;
    this.serverAction = "/upload";
    this.serverOptimizeAction = "/optimize";

    this.dataFileChosen = false;

    this.editor = null;
    this.editorWidth = this.width - 5;
    this.editorHeight = this.height - 83;

    this.resize = this.onResize.bind(this);

    if (this.settings.optimization_backend)
    {
        this.loadCounter = 2; // because 2 xmls to be loaded
    }
    else
        this.loadCounter = 1;
}

//Input.method("recalculateEditorSize", function()
//{
//    this.editorWidth = this.window.width - 5;
//    this.editorHeight = this.window.height - 83;
//});

Input.method("onInitRendered", function()
{
    this.optimizeFlag = 1;
    this.addInstancesFlag = 1;
    this.previousData = null;
    this.toCancel = false;

    $("#submitFile").click(this.submitFileCall.bind(this));
    $("#submitExample").click(this.submitExampleCall.bind(this));
    $("#submitText").click(this.submitTextCall.bind(this));
    $("#submitExample").attr("disabled", "disabled");

    $("#submitFile").attr("disabled", "disabled");

    $("#myform [type='file']").change(this.inputChange.bind(this));
    $("#exampleURL").change(this.exampleChange.bind(this));
    $("#loadExampleInEditor").change(this.exampleChange.bind(this));
//    $("#saveSourceButton").click(this.saveSourceCall.bind(this));

    var options = new Object();
    options.beforeSubmit = this.beginQuery.bind(this);
    options.success = this.fileSent.bind(this);
    options.error = this.handleError.bind(this);
    options.timeout = this.requestTimeout;

    $('#myform').ajaxForm(options);

//    var options = new Object();
//    options.error = this.handleError.bind(this);
//    options.timeout = this.requestTimeout;

//    $('#optimizeForm').ajaxForm(options);

//    var optionsForFile = new Object();
//    optionsForFile.success = this.saveSourceSuccess.bind(this);
//    optionsForFile.error = this.handleError.bind(this);
//    optionsForFile.timeout = this.requestTimeout;
//    $('#saveSourceForm').ajaxForm(optionsForFile);

    this.editor = ace.edit("clafer_editor");
    this.editor.setTheme("ace/theme/eclipse");
    var ClaferMode = require("ace/mode/clafer").Mode;
    this.editor.getSession().setMode(new ClaferMode());
    this.editor.setShowPrintMargin(false);

    // $('#myform').submit(); MOVED TO another location
});

/*
 * Cancel request
 */

Input.method("cancelCall", function()
{
    $("#cancel").hide();
    $("#status_label").html("Cancelling...");
    this.toCancel = true;
});

/*
 * Shows uploader and hides the form
*/
Input.method("beginQuery", function(formData, jqForm, options) {

    if (!this.settings.onBeginQuery(this))
    {
        return false;
    }

    $("#load_area #myform").hide();
    $("#load_area").append('
Loading and processing...<button id="cancel">Cancel</button>
');
    $("#cancel").click(this.cancelCall.bind(this));

    return true;
});

// post-submit callback
Input.method("endQuery", function()  {
    $("#preloader").remove();
    $("#load_area #myform").show();

    $("#claferFileURL").val(""); // empty the URL

    return true;
});

Input.method("onPoll", function(responseObject)
{
    if (!responseObject)
    {
        this.handleError(null, "empty_argument", null);
        return;
    }

    this.settings.onPoll(this, responseObject);

    if (responseObject.message == "Working")
    {
        this.pollingTimeoutObject = setTimeout(this.poll.bind(this), this.pollingDelay);
    }
    else // finished
    {
        this.settings.onCompleted(this, responseObject);
        this.endQuery();
    }
});

Input.method("poll", function()
{
    var options = new Object();
    options.url = "/poll";
    options.type = "post";
    options.timeout = this.pollingTimeout;
    if (!this.toCancel)
        options.data = {windowKey: this.host.key, command: "ping"};
    else
        options.data = {windowKey: this.host.key, command: "cancel"};

    options.success = this.onPoll.bind(this);
    options.error = this.handleError.bind(this);

    $.ajax(options);
});

Input.method("fileSent", function(responseText, statusText, xhr, $form)  {
    this.toCancel = false;

    if (responseText == "error")
    {
        this.handleError(null, "compile_error", null);
        return;
    }

    if (responseText != "no clafer file submitted")
    {
        this.settings.onFileSent(this);
        this.pollingTimeoutObject = setTimeout(this.poll.bind(this), this.pollingDelay);
    }
    else
    {
        this.endQuery(); // else enable the form anyways
//        this.setClaferModelHTML(this.host.findModule("mdCompiledFormats").lastModel);
    }
});

Input.method("handleError", function(response, statusText, xhr)  {
    clearTimeout(this.pollingTimeoutObject);
    var er = document.getElementById("error_overlay");
    er.style.display = "block";
    var caption = this.settings.onError(this, statusText, response, xhr);

    document.getElementById("error_report").innerHTML = ('Close Message
' + caption + "
");
    document.getElementById("close_error").onclick = function(){
        document.getElementById("error_overlay").style.display = "none";
    };
    this.endQuery();

});

Input.method("onSubmit", function(){
    if (this.pollingTimeoutObject)
        clearTimeout(this.pollingTimeoutObject);
});

Input.method("submitFileCall", function(){

    $("#exampleURL").val(null);
    $("#exampleFlag").val("0");
    this.onSubmit();
});

Input.method("submitExampleCall", function(){
    $("#exampleFlag").val("1");
    this.onSubmit();
});

Input.method("submitTextCall", function(){
    $("#claferText").val(this.editor.getValue());
    $("#exampleFlag").val("2");
    this.onSubmit();
});

Input.method("exampleChange", function(){
    if ($("#exampleURL").val())
    {
        $("#submitExample").removeAttr("disabled");
    }
    else
    {
        $("#submitExample").attr("disabled", "disabled");
    }
});

Input.method("inputChange", function(){
    var filename = $("#myform [type='file']").val();

    if (filename)
    {
        if (filename.substring(filename.length-4) == ".cfr"){
            $("#submitFile").removeAttr("disabled");
            $("#submitFile").val(this.settings.button_file_caption);
        }
        else{ // unknown file
            $("#submitFile").val("Unknown");
            $("#submitFile").attr("disabled", "disabled");
        }
    }
    else{ // no file
        $("#submitFile").attr("disabled", "disabled");
        $("#submitFile").val(this.settings.button_file_caption);
    }

});

Input.method("getInitContent", function()
{
    result = '
';
    result += '<form id="myform" action="' + this.serverAction + '" method="post" enctype="multipart/form-data" style="display: block; height:100%">';

    result += '<input type="hidden" name="claferFileURL" id="claferFileURL" value="' + this.host.claferFileURL + '">';
    result += '<input type="hidden" name="exampleFlag" id="exampleFlag" value="0">';
    result += '<input type="hidden" id="windowKey" name="windowKey" value="' + this.host.key + '">';
    result += '<input id="claferText" name="claferText" type="hidden"/>';

    result += '
';
    result += '
';
    result += '<input type="file" size="20" name="claferFile" id="claferFile" title="If you want to upload your clafer file, select one here "/>	';
    result += '<input id="submitFile" type="submit" value="' + this.settings.button_file_caption + '" title="' + this.settings.button_file_tooltip + '"/>	';
    result += '<input id="loadExampleInEditor" type="checkbox" name="loadExampleInEditor" value="unchecked" title="If checked, the editor window below will be loaded with a file or an example submitted">Load into editor</input>	';
    result += '

';
    result += '<select id="exampleURL" style="width:220px" name="exampleURL" title="If you want, you can choose to compile an example clafer model from the list">';

    result += '</select>	';
    result += '<input id="submitExample" type="submit" value="' + this.settings.button_example_caption + '" title="' + this.settings.button_example_tooltip + '"></input>	';

    result += '
Scopes: <select id="ss" name="ss" title="Choose a scope computing strategy. Scopes are used for instantiation using bounded model checking">';

    result += '<option value="none" title="Disable scope computing strategy. All scopes are to be set to 1">Disabled</option>';
    result += '<option value="simple" selected="selected" title="Fast computation. Scopes are not precise, but this strategy works in most cases">Fast</option>';
    result += '<option value="full" title="Full computation. This method is very slow, but for small models works relatively fast">Full</option>';

    result += '</select>
	';

    result += '

';
    result += '';
    result += 'Or enter your model:	';
    result += '<input id="submitText" type="submit" value="' + this.settings.button_editor_caption + '" title="' + this.settings.button_editor_tooltip + '"/>	';

    result += '
Flags: <input id="args" type="text" style="width:90px;" name="args" value="' + this.settings.input_default_flags + '" title="You can specify any additional compilation flags supported by the compiler"></input>
	';

    var padding = "";
    if (this.settings.optimization_backend)
    {
        padding = 'padding-bottom:35px;';
    }

    result += '

';

    result += '
	';

    result += '

';

    var context = this;

    if (this.settings.optimization_backend)
    {
        result += '
';
        result += '

';

        result += 'Optimization backend: <select id="optimizationBackend" style="width:180px" name="optimizationBackend" title=""></select>';

        result += '<input id="useCache" type="checkbox" name="useCache" value="' + this.settings.input_default_cache + '">Use Cache</input>';

        result += '
';

        $.getJSON('/Backends/backends.json',
            function(data)
            {
                var backends = data.backends;
                var options = "";

                var display = "block";

                for (var i = 0; i < backends.length; i++)
                {
                    options += '<option value="' + backends[i].id + '" title="' + backends[i].tooltip + '">' + backends[i].label + '</option>';
                }

                $("#optimizationBackend").html(options);

                context.partLoaded();
            }
        ).error(function()
            {
                var options = '<option value="">(Could not load instance generators)</option>';
                $("#optimizationBackend").html(options);
                context.partLoaded();

            });
    }

    result += '</form>';

//    result += '<form id="saveSourceForm" action="/savesource" method="post" enctype="multipart/form-data">';
//    result += '<input type="hidden" name="windowKey" value="' + this.host.key + '"/>';
//    result += '<input type="hidden" name="saveSourceField" id="saveSourceField" value=""></form>';

    var context = this;

    $.getJSON('/Examples/examples.json',
        function(data)
        {
            var examples = data.examples;
            var options = "";

            for (var i = 0; i < examples.length; i++)
            {
                var optionClass = 'normal_option';

                if (i == 0)
                    optionClass = 'first_option';

                options += '<option class="' + optionClass + '" value="' + examples[i].url + '">' + examples[i].label + '</option>';
            }

            $("#exampleURL").html(options);

            context.partLoaded();

        }
    ).error(function()
        {
            var optionClass = 'first_option';
            var options = '<option class="' + optionClass + '" value="">Or Choose Example (Could not load examples)</option>';
            $("#exampleURL").html(options);
            context.partLoaded();

        });

    return result;

});

Input.method("onResize", function()
{
    this.editor.resize();
});

Input.method("partLoaded", function()
{
    this.loadCounter = this.loadCounter - 1;

    if (this.loadCounter == 0)
    {
       this.host.loaded(this); // notify about getting fully loaded
    }
});

function unescapeJSON(escaped)
{
    return escaped
        .replaceAll('\\\\', '\\')
        .replaceAll('\\"', '"')
        .replaceAll('\\/', '/')
        .replaceAll('\\b', '\b')
        .replaceAll('\\f', '\f')
        .replaceAll('\\n', '\n')
        .replaceAll('\\r', '\r')
        .replaceAll('\\t', '\t');
}
<|endoftext|>


<commit_before>/* global ngDefine: false */
ngDefine('camunda.common.directives.paginator', [], function(module) {
  'use strict';

  var paginatorTmpl =
    '
' +
    '
' +
    '
' +
    '      ' +
    '      ' +
    '
' +
    '
' +
    '
';

  var PaginatorDirective = function () {
    return {
      restrict: 'EAC',
      template: paginatorTmpl,
      replace: true,
      scope: {
        totalPages: '=',
        currentPage: '='
      },
      link: function(scope /*, element, attrs*/) {

        /* constants */

        var LABEL = {
          PREVIOUS: '«',
          NEXT: '»',
          DOTS: '…'
        };

        scope.$watch('totalPages', function() {
          update();
        });

        scope.$watch('currentPage', function() {
          update();
        });

        function update() {
          var totalPages = scope.totalPages;
          var currentPage = scope.currentPage;

          var pages = scope.pages = [];

          if ((!totalPages && totalPages !== 0) ||
              (!currentPage && currentPage !== 0)) {

            return;
          }

          var i;
          if (totalPages > 10) {

            // more than 10 pages, show
            //   1 ... 4 [5] 6 ... n
            for (i = currentPage - 1; i < currentPage + 2; i++) {
              if (i > 0 && i <= totalPages) {
                pages.push({ number: i, text: i, current: (currentPage === i) });
              }
            }

            if (currentPage - 1 > 2) {
              pages.unshift({ disabled: true, text: LABEL.DOTS });
            }

            if (currentPage - 1 > 1) {
              pages.unshift({ number: 1, text: 1 });
            }

            if (currentPage + 2 < totalPages) {
              pages.push({ disabled: true, text: LABEL.DOTS });
            }

            if (currentPage + 1 < totalPages) {
              pages.push({ number: totalPages, text: totalPages });
            }
          } else {

            // less/eq 10 pages, show
            //   1 2 3 4 5 6
            for (i = 1; i <= totalPages; i++) {
              pages.push({ number: i, text: i, current: (currentPage === i) });
            }
          }

          // add << and >> handles

          pages.push({ number: (currentPage + 1), text: LABEL.NEXT, disabled: (currentPage + 1 > totalPages) });
          pages.unshift({ number: (currentPage - 1), text: LABEL.PREVIOUS, disabled: (currentPage - 1 < 1)});
        }

        scope.selectPage = function(page) {
          scope.currentPage = page;
        };
      }
    };
  };

  module.directive('paginator', PaginatorDirective);
});
<commit_msg>fix(cockpit/paginator): fix paginator module definition<commit_after>/* global ngDefine: false */
ngDefine('camunda.common.directives', [], function(module) {
  'use strict';

  var paginatorTmpl =
    '
' +
    '
' +
    '
' +
    '      ' +
    '      ' +
    '
' +
    '
' +
    '
';

  var PaginatorDirective = function () {
    return {
      restrict: 'EAC',
      template: paginatorTmpl,
      replace: true,
      scope: {
        totalPages: '=',
        currentPage: '='
      },
      link: function(scope /*, element, attrs*/) {

        /* constants */

        var LABEL = {
          PREVIOUS: '«',
          NEXT: '»',
          DOTS: '…'
        };

        scope.$watch('totalPages', function() {
          update();
        });

        scope.$watch('currentPage', function() {
          update();
        });

        function update() {
          var totalPages = scope.totalPages;
          var currentPage = scope.currentPage;

          var pages = scope.pages = [];

          if ((!totalPages && totalPages !== 0) ||
              (!currentPage && currentPage !== 0)) {

            return;
          }

          var i;
          if (totalPages > 10) {

            // more than 10 pages, show
            //   1 ... 4 [5] 6 ... n
            for (i = currentPage - 1; i < currentPage + 2; i++) {
              if (i > 0 && i <= totalPages) {
                pages.push({ number: i, text: i, current: (currentPage === i) });
              }
            }

            if (currentPage - 1 > 2) {
              pages.unshift({ disabled: true, text: LABEL.DOTS });
            }

            if (currentPage - 1 > 1) {
              pages.unshift({ number: 1, text: 1 });
            }

            if (currentPage + 2 < totalPages) {
              pages.push({ disabled: true, text: LABEL.DOTS });
            }

            if (currentPage + 1 < totalPages) {
              pages.push({ number: totalPages, text: totalPages });
            }
          } else {

            // less/eq 10 pages, show
            //   1 2 3 4 5 6
            for (i = 1; i <= totalPages; i++) {
              pages.push({ number: i, text: i, current: (currentPage === i) });
            }
          }

          // add << and >> handles

          pages.push({ number: (currentPage + 1), text: LABEL.NEXT, disabled: (currentPage + 1 > totalPages) });
          pages.unshift({ number: (currentPage - 1), text: LABEL.PREVIOUS, disabled: (currentPage - 1 < 1)});
        }

        scope.selectPage = function(page) {
          scope.currentPage = page;
        };
      }
    };
  };

  module.directive('paginator', PaginatorDirective);
});
<|endoftext|>


<commit_before>ҵ˵<commit_msg>delete test.js<commit_after><|endoftext|>


<commit_before>'use strict';

var testHelper = require('../../test-helper');
var setupFile = require('./authorizations-setup');

var authorizationsPage = require('../pages/authorizations');
var cockpitPage = require('../../cockpit/pages/dashboard');

describe('Admin authorizations Spec', function() {

  function checkCreateNewState() {

    // then
    expect(authorizationsPage.createNewElement().isDisplayed()).to.eventually.be.true;
    expect(authorizationsPage.submitNewAuthorizationButton().isEnabled()).to.eventually.be.false;
    expect(authorizationsPage.abortNewAuthorizationButton().isEnabled()).to.eventually.be.true;
    expect(authorizationsPage.resourceIdField().getAttribute('value')).to.eventually.eql('*');
  }

  function checkAuthorizationTypes() {

    authorizationsPage.authorizationType('GLOBAL').click();
    expect(authorizationsPage.identityIdInputFiled().isEnabled()).to.eventually.be.false;

    authorizationsPage.authorizationType('DENY').click();
    expect(authorizationsPage.identityIdInputFiled().isEnabled()).to.eventually.be.true;

    authorizationsPage.authorizationType('ALLOW').click();
    expect(authorizationsPage.identityIdInputFiled().isEnabled()).to.eventually.be.true;
  }

  function checkPermissionTypes(permissionsList, permissionDefaultValue) {

    // when
    authorizationsPage.permissionsButton().click();

    // then
    for (var i = 0; i < permissionsList.length; i++) {
      expect(authorizationsPage.permissionsDropdownList().get(i).getText()).to.eventually.eql(permissionsList[i]);
    };

    if (arguments.length !== 2) {
      permissionDefaultValue = 'ALL';
    }
    expect(authorizationsPage.permissionsField().getText()).to.eventually.eql(permissionDefaultValue);
  }

  function abortCreatingNewAuthorization() {

     // when
    authorizationsPage.abortNewAuthorizationButton().click();

    // then
    expect(authorizationsPage.createNewElement().isDisplayed()).to.eventually.be.false;
  }

  describe('Application Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to application page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Application');

      // then
      authorizationsPage.application.isActive();
      expect(authorizationsPage.application.createNewButton().isEnabled()).to.eventually.be.true;
      expect(authorizationsPage.application.boxHeader()).to.eventually.eql('Application Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'ACCESS',
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

    it('should create new application authorization', function() {

      // when
      authorizationsPage.application.createNewAuthorization('ALLOW', 'USER', 'john', 'ACCESS', 'cockpit');

      // then
      authorizationsPage.logout();
      cockpitPage.navigateToWebapp('Cockpit');
      cockpitPage.authentication.userLogin('john', 'MobyDick');
      cockpitPage.isActive();
    });

  });

  describe('Authorization Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to authorization page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Authorization');

      // then
      authorizationsPage.authorization.isActive();
      expect(authorizationsPage.authorization.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.authorization.boxHeader()).to.eventually.eql('Authorization Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Deployment Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to deployment page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Deployment');

      // then
      authorizationsPage.deployment.isActive();
      expect(authorizationsPage.deployment.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.deployment.boxHeader()).to.eventually.eql('Deployment Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'READ',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Filter Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to filter page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Filter');

      // then
      authorizationsPage.filter.isActive();
      expect(authorizationsPage.filter.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.filter.boxHeader()).to.eventually.eql('Filter Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Group Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to group page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Group');

      // then
      authorizationsPage.group.isActive();
      expect(authorizationsPage.group.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.group.boxHeader()).to.eventually.eql('Group Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Group Membership Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to group membership page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Group Membership');

      // then
      authorizationsPage.groupMembership.isActive();
      expect(authorizationsPage.groupMembership.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.groupMembership.boxHeader()).to.eventually.eql('Group Membership Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Process Definition Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to process definition page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Process Definition');

      // then
      authorizationsPage.processDefinition.isActive();
      expect(authorizationsPage.processDefinition.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.processDefinition.boxHeader()).to.eventually.eql('Process Definition Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'CREATE_INSTANCE',
          'READ_INSTANCE',
          'UPDATE_INSTANCE',
          'DELETE_INSTANCE',
          'READ_TASK',
          'UPDATE_TASK'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

    it('should create new authorization', function() {

      // when
      authorizationsPage.application.createNewAuthorization('ALLOW', 'GROUP', 'marketing', 'READ_INSTANCE', 'invoice');

      // then

    });

  });

  describe('Process Instance Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to process instance page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Process Instance');

      // then
      authorizationsPage.processInstance.isActive();
      expect(authorizationsPage.processInstance.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.processInstance.boxHeader()).to.eventually.eql('Process Instance Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'READ',
          'UPDATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Task Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to task page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Task');

      // then
      authorizationsPage.task.isActive();
      expect(authorizationsPage.task.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.task.boxHeader()).to.eventually.eql('Task Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'READ',
          'UPDATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('User Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to user page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('User');

      // then
      authorizationsPage.user.isActive();
      expect(authorizationsPage.user.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.user.boxHeader()).to.eventually.eql('User Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

});
<commit_msg>chore(admin/test): adjust authorization test case<commit_after>'use strict';

var testHelper = require('../../test-helper');
var setupFile = require('./authorizations-setup');

var authorizationsPage = require('../pages/authorizations');
var cockpitPage = require('../../cockpit/pages/dashboard');

describe('Admin authorizations Spec', function() {

  function checkCreateNewState() {

    // then
    expect(authorizationsPage.createNewElement().isDisplayed()).to.eventually.be.true;
    expect(authorizationsPage.submitNewAuthorizationButton().isEnabled()).to.eventually.be.false;
    expect(authorizationsPage.abortNewAuthorizationButton().isEnabled()).to.eventually.be.true;
    expect(authorizationsPage.resourceIdField().getAttribute('value')).to.eventually.eql('*');
  }

  function checkAuthorizationTypes() {

    authorizationsPage.authorizationType('GLOBAL').click();
    expect(authorizationsPage.identityIdInputFiled().isEnabled()).to.eventually.be.false;

    authorizationsPage.authorizationType('DENY').click();
    expect(authorizationsPage.identityIdInputFiled().isEnabled()).to.eventually.be.true;

    authorizationsPage.authorizationType('ALLOW').click();
    expect(authorizationsPage.identityIdInputFiled().isEnabled()).to.eventually.be.true;
  }

  function checkPermissionTypes(permissionsList, permissionDefaultValue) {

    // when
    authorizationsPage.permissionsButton().click();

    // then
    for (var i = 0; i < permissionsList.length; i++) {
      expect(authorizationsPage.permissionsDropdownList().get(i).getText()).to.eventually.eql(permissionsList[i]);
    };

    if (arguments.length !== 2) {
      permissionDefaultValue = 'ALL';
    }
    expect(authorizationsPage.permissionsField().getText()).to.eventually.eql(permissionDefaultValue);
  }

  function abortCreatingNewAuthorization() {

     // when
    authorizationsPage.abortNewAuthorizationButton().click();

    // then
    expect(authorizationsPage.createNewElement().isDisplayed()).to.eventually.be.false;
  }

  describe('Application Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to application page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Application');

      // then
      authorizationsPage.application.isActive();
      expect(authorizationsPage.application.createNewButton().isEnabled()).to.eventually.be.true;
      expect(authorizationsPage.application.boxHeader()).to.eventually.eql('Application Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'ACCESS',
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

    it('should create new application authorization', function() {

      // when
      authorizationsPage.application.createNewAuthorization('ALLOW', 'USER', 'john', 'ACCESS', 'cockpit');

      // then
      authorizationsPage.logout();
      cockpitPage.navigateToWebapp('Cockpit');
      cockpitPage.authentication.userLogin('john', 'MobyDick');
      cockpitPage.isActive();
    });

  });

  describe('Authorization Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to authorization page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Authorization');

      // then
      authorizationsPage.authorization.isActive();
      expect(authorizationsPage.authorization.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.authorization.boxHeader()).to.eventually.eql('Authorization Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Deployment Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to deployment page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Deployment');

      // then
      authorizationsPage.deployment.isActive();
      expect(authorizationsPage.deployment.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.deployment.boxHeader()).to.eventually.eql('Deployment Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'READ',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Filter Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to filter page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Filter');

      // then
      authorizationsPage.filter.isActive();
      expect(authorizationsPage.filter.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.filter.boxHeader()).to.eventually.eql('Filter Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Group Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to group page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Group');

      // then
      authorizationsPage.group.isActive();
      expect(authorizationsPage.group.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.group.boxHeader()).to.eventually.eql('Group Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Group Membership Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to group membership page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Group Membership');

      // then
      authorizationsPage.groupMembership.isActive();
      expect(authorizationsPage.groupMembership.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.groupMembership.boxHeader()).to.eventually.eql('Group Membership Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Process Definition Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to process definition page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Process Definition');

      // then
      authorizationsPage.processDefinition.isActive();
      expect(authorizationsPage.processDefinition.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.processDefinition.boxHeader()).to.eventually.eql('Process Definition Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'CREATE_INSTANCE',
          'READ_INSTANCE',
          'UPDATE_INSTANCE',
          'DELETE_INSTANCE',
          'READ_TASK',
          'UPDATE_TASK',
          'READ_HISTORY',
          'DELETE_HISTORY'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

    it('should create new authorization', function() {

      // when
      authorizationsPage.application.createNewAuthorization('ALLOW', 'GROUP', 'marketing', 'READ_INSTANCE', 'invoice');

      // then

    });

  });

  describe('Process Instance Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to process instance page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Process Instance');

      // then
      authorizationsPage.processInstance.isActive();
      expect(authorizationsPage.processInstance.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.processInstance.boxHeader()).to.eventually.eql('Process Instance Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'READ',
          'UPDATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('Task Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to task page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('Task');

      // then
      authorizationsPage.task.isActive();
      expect(authorizationsPage.task.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.task.boxHeader()).to.eventually.eql('Task Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'CREATE',
          'READ',
          'UPDATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

  describe('User Authorizations', function() {

    before(function() {
      return testHelper(setupFile, function() {

        authorizationsPage.navigateToWebapp('Admin');
        authorizationsPage.authentication.userLogin('admin', 'admin');

        authorizationsPage.selectNavbarItem('Authorizations');
      });
    });

    it('should navigate to user page', function() {

      // when
      authorizationsPage.selectAuthorizationNavbarItem('User');

      // then
      authorizationsPage.user.isActive();
      expect(authorizationsPage.user.createNewButton().isEnabled()).to.eventually.eql(true);
      expect(authorizationsPage.user.boxHeader()).to.eventually.eql('User Authorizations');
    });

    it('should validate authorization attributes', function() {

      authorizationsPage.createNewButton().click().then(function() {

        checkCreateNewState();

        checkAuthorizationTypes();

        var permissionsList = [
          'READ',
          'UPDATE',
          'CREATE',
          'DELETE'
        ];
        checkPermissionTypes(permissionsList);

        abortCreatingNewAuthorization();
      });

    });

  });

});
<|endoftext|>


<commit_before>import Geo from './geo';
import {StyleParser} from './styles/style_parser';
import {StyleManager} from './styles/style_manager';
import Collision from './labels/collision';
import WorkerBroker from './utils/worker_broker';
import Texture from './gl/texture';

import log from 'loglevel';

export default class Tile {

    /**
        Tile
        @constructor
        Required properties:
        coords: object with {x, y, z} properties identifying tile coordinate location
        worker: web worker to handle tile construction
    */
    constructor({ coords, style_zoom, source, worker, tile_manager }) {
        Object.assign(this, {
            coords: {
                x: null,
                y: null,
                z: null
            },
            debug: {},
            loading: false,
            loaded: false,
            error: null,
            worker: null,
            generation: null,
            visible: false,
            center_dist: 0
        });

        this.worker = worker;
        this.tile_manager = tile_manager;
        this.source = source;
        this.style_zoom = style_zoom; // zoom level to be used for styling

        this.coords = coords;
        this.coords = Tile.overZoomedCoordinate(this.coords, this.source.max_zoom);
        this.coord_key = Tile.coordKey(this.coords);
        this.key = Tile.key(this.coords, this.source, this.style_zoom);
        this.min = Geo.metersForTile(this.coords);
        this.max = Geo.metersForTile({x: this.coords.x + 1, y: this.coords.y + 1, z: this.coords.z }),
        this.span = { x: (this.max.x - this.min.x), y: (this.max.y - this.min.y) };
        this.bounds = { sw: { x: this.min.x, y: this.max.y }, ne: { x: this.max.x, y: this.min.y } };

        // Units per pixel needs to account for over-zooming
        this.units_per_pixel = Geo.units_per_pixel;
        if (this.style_zoom > this.coords.z) {
            this.units_per_pixel /= Math.pow(2, this.style_zoom - this.coords.z);
        }

        this.meters_per_pixel = Geo.metersPerPixel(this.coords.z);
        this.units_per_meter = Geo.unitsPerMeter(this.coords.z);

        this.meshes = {}; // renderable VBO meshes keyed by style
        this.textures = []; // textures that the tile owns (labels, etc.)
    }

    static create(spec) {
        return new Tile(spec);
    }

    static coordKey({x, y, z}) {
        return [x, y, z].join('/');
    }

    static key (coords, source, style_zoom) {
        coords = Tile.overZoomedCoordinate(coords, source.max_zoom);
        if (coords.y < 0 || coords.y >= (1 << coords.z) || coords.z < 0) {
            return; // cull tiles out of range (x will wrap)
        }
        return [source.name, style_zoom, coords.x, coords.y, coords.z].join('/');
    }

    static coordinateAtZoom({x, y, z}, zoom) {
        if (z !== zoom) {
            let zscale = Math.pow(2, z - zoom);
            x = Math.floor(x / zscale);
            y = Math.floor(y / zscale);
        }
        return {x, y, z: zoom};
    }

    static isChild(parent, child) {
        if (child.z > parent.z) {
            let {x, y} = Tile.coordinateAtZoom(child, parent.z);
            return (parent.x === x && parent.y === y);
        }
        return false;
    }

    static overZoomedCoordinate({x, y, z}, max_zoom) {
        if (max_zoom !== undefined && z > max_zoom) {
            return Tile.coordinateAtZoom({x, y, z}, max_zoom);
        }
        return {x, y, z};
    }

    // Sort a set of tile instances (which already have a distance from center tile computed)
    static sort(tiles) {
        return tiles.sort((a, b) => {
            let ad = a.center_dist;
            let bd = b.center_dist;
            return (bd > ad ? -1 : (bd === ad ? 0 : 1));
        });
    }

    freeResources() {
        if (this.meshes) {
            for (let m in this.meshes) {
                this.meshes[m].destroy();
            }
        }

        if (this.textures) {
            for (let t of this.textures) {
                let texture = Texture.textures[t];
                if (texture) {
                    texture.destroy();
                }
            }
        }

        this.meshes = {};
        this.textures = [];
    }

    destroy() {
        this.workerMessage('self.removeTile', this.key);
        this.freeResources();
        this.worker = null;
    }

    buildAsMessage() {
        return {
            key: this.key,
            coord_key: this.coord_key,
            source: this.source.name,
            coords: this.coords,
            min: this.min,
            max: this.max,
            units_per_pixel: this.units_per_pixel,
            meters_per_pixel: this.meters_per_pixel,
            units_per_meter: this.units_per_meter,
            style_zoom: this.style_zoom,
            generation: this.generation,
            debug: this.debug
        };
    }

    workerMessage (...message) {
        return WorkerBroker.postMessage(this.worker, ...message);
    }

    build(generation) {
        this.generation = generation;
        if (!this.loaded) {
            this.loading = true;
        }
        return this.workerMessage('self.buildTile', { tile: this.buildAsMessage() }).catch(e => { throw e; });
    }

    // Process geometry for tile - called by web worker
    // Returns a set of tile keys that should be sent to the main thread (so that we can minimize data exchange between worker and main thread)
    static buildGeometry (tile, layers, rules, styles) {
        tile.debug.rendering = +new Date();
        tile.debug.features = 0;

        let data = tile.source_data;

        Collision.startTile(tile.key);

        // Treat top-level style rules as 'layers'
        for (let layer_name in layers) {
            let layer = layers[layer_name];
            // Skip layers with no data source defined
            if (!layer || !layer.data) {
                log.warn(`Layer ${layer} was defined without a geometry data source and will not be rendered.`);
                continue;
            }

            // Source names don't match
            if (layer.data.source !== tile.source) {
                continue;
            }

            // Get data for one or more layers from source
            let source_layers = Tile.getDataForSource(data, layer.data, layer_name);

            // Render features in layer
            for (let s=0; s < source_layers.length; s++) {
                let source_layer = source_layers[s];
                let geom = source_layer.geom;
                if (!geom) {
                    continue;
                }

                for (let f = 0; f < geom.features.length; f++) {
                    let feature = geom.features[f];
                    if (feature.geometry == null) {
                        continue; // skip features w/o geometry (valid GeoJSON)
                    }

                    let context = StyleParser.getFeatureParseContext(feature, tile);
                    context.winding = tile.default_winding;
                    context.layer = source_layer.layer; // add data source layer name

                    // Get draw groups for this feature
                    let layer_rules = rules[layer_name];
                    let draw_groups = layer_rules.buildDrawGroups(context, true);
                    if (!draw_groups) {
                        continue;
                    }

                    // Render draw groups
                    for (let group_name in draw_groups) {
                        let group = draw_groups[group_name];
                        if (!group.visible) {
                            continue;
                        }

                        // Add to style
                        let style_name = group.style || group_name;
                        let style = styles[style_name];

                        if (!style) {
                            log.warn(`Style '${style_name}' not found for rule in layer '${layer_name}':`, group, feature);
                            continue;
                        }

                        context.properties = group.properties; // add rule-specific properties to context

                        style.addFeature(feature, group, context);

                        context.properties = null; // clear group-specific properties
                    }

                    tile.debug.features++;
                }
            }
        }
        tile.debug.rendering = +new Date() - tile.debug.rendering;

        // Finalize array buffer for each render style
        let tile_styles = StyleManager.stylesForTile(tile.key);
        tile.mesh_data = {};
        let queue = [];
        for (let s=0; s < tile_styles.length; s++) {
            let style_name = tile_styles[s];
            let style = styles[style_name];
            queue.push(style.endData(tile).then((style_data) => {
                if (style_data) {
                    tile.mesh_data[style_name] = {
                        vertex_data: style_data.vertex_data,
                        uniforms: style_data.uniforms,
                        textures: style_data.textures
                    };
                }
            }));
        }

        return Promise.all(queue).then(() => {
            Collision.resetTile(tile.key);

            // Return keys to be transfered to main thread
            return ['mesh_data'];
        });
    }

    /**
        Retrieves geometry from a tile according to a data source definition
        Returns an array of objects with:
            layer: source layer name
            geom: GeoJSON FeatureCollection
    */
    static getDataForSource (source_data, source_config, default_layer = null) {
        var layers = [];

        if (source_config != null) {
            // If no layer specified, and a default source layer exists
            if (!source_config.layer && source_data.layers._default) {
                layers.push({
                    layer: '_default',
                    geom: source_data.layers._default
                });
            }
            // If no layer specified, and a default requested layer exists
            else if (!source_config.layer && default_layer) {
                layers.push({
                    layer: default_layer,
                    geom: source_data.layers[default_layer]
                });
            }
            // If a layer is specified by name, use it
            else if (typeof source_config.layer === 'string') {
                layers.push({
                    layer: source_config.layer,
                    geom: source_data.layers[source_config.layer]
                });
            }
            // If multiple layers are specified by name, combine them
            else if (Array.isArray(source_config.layer)) {
                source_config.layer.forEach(layer => {
                    if (source_data.layers[layer] && source_data.layers[layer].features) {
                        layers.push({
                            layer,
                            geom: source_data.layers[layer]
                        });
                    }
                });
            }
            // Assemble a custom layer via a function, which is called with all source layers
            else if (typeof source_config.layer === 'function') {
                layers.push({
                    geom: source_config.layer(source_data.layers)
                    // custom layer has no name
                });
            }
        }

        return layers;
    }

    /**
       Called on main thread when a web worker completes processing
       for a single tile.
    */
    buildMeshes(styles) {
        if (this.error) {
            return;
        }

        // Cleanup existing VBOs
        this.freeResources();

        // Debug
        this.debug.geometries = 0;
        this.debug.buffer_size = 0;

        // Create VBOs
        let mesh_data = this.mesh_data;
        if (mesh_data) {
            for (var s in mesh_data) {
                if (mesh_data[s].vertex_data) {
                    this.debug.buffer_size += mesh_data[s].vertex_data.byteLength;
                    if (!styles[s]) {
                        log.warn(`Could not create mesh because style '${s}' not found, for tile ${this.key}, aborting tile`);
                        this.meshes = {};
                        break;
                    }
                    this.meshes[s] = styles[s].makeMesh(mesh_data[s].vertex_data, mesh_data[s]);
                    this.debug.geometries += this.meshes[s].geometry_count;
                }

                // Assign ownership to textures if needed
                if (mesh_data[s].textures) {
                    this.textures.push(...mesh_data[s].textures);
                }
            }
        }

        this.debug.geom_ratio = (this.debug.geometries / this.debug.features).toFixed(1);
        this.mesh_data = null; // TODO: might want to preserve this for rebuilding geometries when styles/etc. change?
        this.printDebug();
    }

    /**
        Called on main thread when web worker completes processing, but tile has since been discarded
        Frees resources that would have been transferred to the tile object.
        Static method because the tile object no longer exists (the tile data returned by the worker is passed instead).
    */
    static abortBuild (tile) {
        if (tile.mesh_data) {
            for (let s in tile.mesh_data) {
                let textures = tile.mesh_data[s].textures;
                if (textures) {
                    for (let t of textures) {
                        let texture = Texture.textures[t];
                        if (texture) {
                            log.trace(`destroying texture ${t} for tile ${tile.key}`);
                            texture.destroy();
                        }
                    }
                }
            }
        }
    }

    printDebug () {
        log.debug(`Tile: debug for ${this.key}: [  ${JSON.stringify(this.debug)} ]`);
    }

    update() {
        let scene = this.tile_manager.scene;
        let coords = this.coords;
        if (coords.z !== scene.center_tile.z) {
            coords = Tile.coordinateAtZoom(coords, scene.center_tile.z);
        }
        this.center_dist = Math.abs(scene.center_tile.x - coords.x) + Math.abs(scene.center_tile.y - coords.y);
    }

    // Slice a subset of keys out of a tile
    // Includes a minimum set of pre-defined keys for load state, debug. etc.
    // We use this to send a subset of the tile back to the main thread, to minimize unnecessary data transfer
    // (e.g. very large items like feature geometry are not needed on the main thread)
    static slice (tile, keys) {
        let keep = [
            'key',
            'loading',
            'loaded',
            'generation',
            'error',
            'debug'
        ];
        if (Array.isArray(keys)) {
            keep.push(...keys);
        }

        // Build the tile subset
        var tile_subset = {};
        for (let key of keep) {
            tile_subset[key] = tile[key];
        }

        return tile_subset;
    }

    /**
        Called on worker to cancel loading
        Static method because the worker only has object representations of tile data, there is no
        tile instance created yet.
    */
    static cancel(tile) {
        if (tile) {
            if (tile.source_data && tile.source_data.request) {
                tile.source_data.request.abort();
            }
            Tile.abortBuild(tile);
        }
    }

    merge(other) {
        for (var key in other) {
            if (key !== 'key') {
                this[key] = other[key];
            }
        }
        return this;
    }

}
<commit_msg>when rebuilding, preserve textures that tile will retain ownership of prevents flickering when tile is rebuilt but underlying textures aren't changing<commit_after>import Geo from './geo';
import {StyleParser} from './styles/style_parser';
import {StyleManager} from './styles/style_manager';
import Collision from './labels/collision';
import WorkerBroker from './utils/worker_broker';
import Texture from './gl/texture';

import log from 'loglevel';

export default class Tile {

    /**
        Tile
        @constructor
        Required properties:
        coords: object with {x, y, z} properties identifying tile coordinate location
        worker: web worker to handle tile construction
    */
    constructor({ coords, style_zoom, source, worker, tile_manager }) {
        Object.assign(this, {
            coords: {
                x: null,
                y: null,
                z: null
            },
            debug: {},
            loading: false,
            loaded: false,
            error: null,
            worker: null,
            generation: null,
            visible: false,
            center_dist: 0
        });

        this.worker = worker;
        this.tile_manager = tile_manager;
        this.source = source;
        this.style_zoom = style_zoom; // zoom level to be used for styling

        this.coords = coords;
        this.coords = Tile.overZoomedCoordinate(this.coords, this.source.max_zoom);
        this.coord_key = Tile.coordKey(this.coords);
        this.key = Tile.key(this.coords, this.source, this.style_zoom);
        this.min = Geo.metersForTile(this.coords);
        this.max = Geo.metersForTile({x: this.coords.x + 1, y: this.coords.y + 1, z: this.coords.z }),
        this.span = { x: (this.max.x - this.min.x), y: (this.max.y - this.min.y) };
        this.bounds = { sw: { x: this.min.x, y: this.max.y }, ne: { x: this.max.x, y: this.min.y } };

        // Units per pixel needs to account for over-zooming
        this.units_per_pixel = Geo.units_per_pixel;
        if (this.style_zoom > this.coords.z) {
            this.units_per_pixel /= Math.pow(2, this.style_zoom - this.coords.z);
        }

        this.meters_per_pixel = Geo.metersPerPixel(this.coords.z);
        this.units_per_meter = Geo.unitsPerMeter(this.coords.z);

        this.meshes = {}; // renderable VBO meshes keyed by style
        this.textures = []; // textures that the tile owns (labels, etc.)
    }

    static create(spec) {
        return new Tile(spec);
    }

    static coordKey({x, y, z}) {
        return [x, y, z].join('/');
    }

    static key (coords, source, style_zoom) {
        coords = Tile.overZoomedCoordinate(coords, source.max_zoom);
        if (coords.y < 0 || coords.y >= (1 << coords.z) || coords.z < 0) {
            return; // cull tiles out of range (x will wrap)
        }
        return [source.name, style_zoom, coords.x, coords.y, coords.z].join('/');
    }

    static coordinateAtZoom({x, y, z}, zoom) {
        if (z !== zoom) {
            let zscale = Math.pow(2, z - zoom);
            x = Math.floor(x / zscale);
            y = Math.floor(y / zscale);
        }
        return {x, y, z: zoom};
    }

    static isChild(parent, child) {
        if (child.z > parent.z) {
            let {x, y} = Tile.coordinateAtZoom(child, parent.z);
            return (parent.x === x && parent.y === y);
        }
        return false;
    }

    static overZoomedCoordinate({x, y, z}, max_zoom) {
        if (max_zoom !== undefined && z > max_zoom) {
            return Tile.coordinateAtZoom({x, y, z}, max_zoom);
        }
        return {x, y, z};
    }

    // Sort a set of tile instances (which already have a distance from center tile computed)
    static sort(tiles) {
        return tiles.sort((a, b) => {
            let ad = a.center_dist;
            let bd = b.center_dist;
            return (bd > ad ? -1 : (bd === ad ? 0 : 1));
        });
    }

    // Free resources owned by tile
    // Optionally pass textures to preserve
    freeResources(preserve = {}) {
        if (this.meshes) {
            for (let m in this.meshes) {
                this.meshes[m].destroy();
            }
        }

        if (this.textures) {
            for (let t of this.textures) {
                if (!preserve.textures || preserve.textures.indexOf(t) === -1) {
                    let texture = Texture.textures[t];
                    if (texture) {
                        texture.destroy();
                    }
                }
            }
        }

        this.meshes = {};
        this.textures = [];
    }

    destroy() {
        this.workerMessage('self.removeTile', this.key);
        this.freeResources();
        this.worker = null;
    }

    buildAsMessage() {
        return {
            key: this.key,
            coord_key: this.coord_key,
            source: this.source.name,
            coords: this.coords,
            min: this.min,
            max: this.max,
            units_per_pixel: this.units_per_pixel,
            meters_per_pixel: this.meters_per_pixel,
            units_per_meter: this.units_per_meter,
            style_zoom: this.style_zoom,
            generation: this.generation,
            debug: this.debug
        };
    }

    workerMessage (...message) {
        return WorkerBroker.postMessage(this.worker, ...message);
    }

    build(generation) {
        this.generation = generation;
        if (!this.loaded) {
            this.loading = true;
        }
        return this.workerMessage('self.buildTile', { tile: this.buildAsMessage() }).catch(e => { throw e; });
    }

    // Process geometry for tile - called by web worker
    // Returns a set of tile keys that should be sent to the main thread (so that we can minimize data exchange between worker and main thread)
    static buildGeometry (tile, layers, rules, styles) {
        tile.debug.rendering = +new Date();
        tile.debug.features = 0;

        let data = tile.source_data;

        Collision.startTile(tile.key);

        // Treat top-level style rules as 'layers'
        for (let layer_name in layers) {
            let layer = layers[layer_name];
            // Skip layers with no data source defined
            if (!layer || !layer.data) {
                log.warn(`Layer ${layer} was defined without a geometry data source and will not be rendered.`);
                continue;
            }

            // Source names don't match
            if (layer.data.source !== tile.source) {
                continue;
            }

            // Get data for one or more layers from source
            let source_layers = Tile.getDataForSource(data, layer.data, layer_name);

            // Render features in layer
            for (let s=0; s < source_layers.length; s++) {
                let source_layer = source_layers[s];
                let geom = source_layer.geom;
                if (!geom) {
                    continue;
                }

                for (let f = 0; f < geom.features.length; f++) {
                    let feature = geom.features[f];
                    if (feature.geometry == null) {
                        continue; // skip features w/o geometry (valid GeoJSON)
                    }

                    let context = StyleParser.getFeatureParseContext(feature, tile);
                    context.winding = tile.default_winding;
                    context.layer = source_layer.layer; // add data source layer name

                    // Get draw groups for this feature
                    let layer_rules = rules[layer_name];
                    let draw_groups = layer_rules.buildDrawGroups(context, true);
                    if (!draw_groups) {
                        continue;
                    }

                    // Render draw groups
                    for (let group_name in draw_groups) {
                        let group = draw_groups[group_name];
                        if (!group.visible) {
                            continue;
                        }

                        // Add to style
                        let style_name = group.style || group_name;
                        let style = styles[style_name];

                        if (!style) {
                            log.warn(`Style '${style_name}' not found for rule in layer '${layer_name}':`, group, feature);
                            continue;
                        }

                        context.properties = group.properties; // add rule-specific properties to context

                        style.addFeature(feature, group, context);

                        context.properties = null; // clear group-specific properties
                    }

                    tile.debug.features++;
                }
            }
        }
        tile.debug.rendering = +new Date() - tile.debug.rendering;

        // Finalize array buffer for each render style
        let tile_styles = StyleManager.stylesForTile(tile.key);
        tile.mesh_data = {};
        let queue = [];
        for (let s=0; s < tile_styles.length; s++) {
            let style_name = tile_styles[s];
            let style = styles[style_name];
            queue.push(style.endData(tile).then((style_data) => {
                if (style_data) {
                    tile.mesh_data[style_name] = {
                        vertex_data: style_data.vertex_data,
                        uniforms: style_data.uniforms,
                        textures: style_data.textures
                    };
                }
            }));
        }

        return Promise.all(queue).then(() => {
            Collision.resetTile(tile.key);

            // Return keys to be transfered to main thread
            return ['mesh_data'];
        });
    }

    /**
        Retrieves geometry from a tile according to a data source definition
        Returns an array of objects with:
            layer: source layer name
            geom: GeoJSON FeatureCollection
    */
    static getDataForSource (source_data, source_config, default_layer = null) {
        var layers = [];

        if (source_config != null) {
            // If no layer specified, and a default source layer exists
            if (!source_config.layer && source_data.layers._default) {
                layers.push({
                    layer: '_default',
                    geom: source_data.layers._default
                });
            }
            // If no layer specified, and a default requested layer exists
            else if (!source_config.layer && default_layer) {
                layers.push({
                    layer: default_layer,
                    geom: source_data.layers[default_layer]
                });
            }
            // If a layer is specified by name, use it
            else if (typeof source_config.layer === 'string') {
                layers.push({
                    layer: source_config.layer,
                    geom: source_data.layers[source_config.layer]
                });
            }
            // If multiple layers are specified by name, combine them
            else if (Array.isArray(source_config.layer)) {
                source_config.layer.forEach(layer => {
                    if (source_data.layers[layer] && source_data.layers[layer].features) {
                        layers.push({
                            layer,
                            geom: source_data.layers[layer]
                        });
                    }
                });
            }
            // Assemble a custom layer via a function, which is called with all source layers
            else if (typeof source_config.layer === 'function') {
                layers.push({
                    geom: source_config.layer(source_data.layers)
                    // custom layer has no name
                });
            }
        }

        return layers;
    }

    /**
       Called on main thread when a web worker completes processing
       for a single tile.
    */
    buildMeshes(styles) {
        if (this.error) {
            return;
        }

        // Debug
        this.debug.geometries = 0;
        this.debug.buffer_size = 0;

        // Create VBOs
        let meshes = {}, textures = []; // new resources, to be swapped in
        let mesh_data = this.mesh_data;
        if (mesh_data) {
            for (var s in mesh_data) {
                if (mesh_data[s].vertex_data) {
                    this.debug.buffer_size += mesh_data[s].vertex_data.byteLength;
                    if (!styles[s]) {
                        log.warn(`Could not create mesh because style '${s}' not found, for tile ${this.key}, aborting tile`);
                        break;
                    }
                    meshes[s] = styles[s].makeMesh(mesh_data[s].vertex_data, mesh_data[s]);
                    this.debug.geometries += meshes[s].geometry_count;
                }

                // Assign ownership to textures if needed
                if (mesh_data[s].textures) {
                    textures.push(...mesh_data[s].textures);
                }
            }
        }
        delete this.mesh_data; // TODO: might want to preserve this for rebuilding geometries when styles/etc. change?

        // Swap in new data, free old data
        this.freeResources({ textures }); // textures to preserve are passed (avoid flickering from delete/re-create)
        this.meshes = meshes;
        this.textures = textures;

        this.debug.geom_ratio = (this.debug.geometries / this.debug.features).toFixed(1);
        this.printDebug();
    }

    /**
        Called on main thread when web worker completes processing, but tile has since been discarded
        Frees resources that would have been transferred to the tile object.
        Static method because the tile object no longer exists (the tile data returned by the worker is passed instead).
    */
    static abortBuild (tile) {
        if (tile.mesh_data) {
            for (let s in tile.mesh_data) {
                let textures = tile.mesh_data[s].textures;
                if (textures) {
                    for (let t of textures) {
                        let texture = Texture.textures[t];
                        if (texture) {
                            log.trace(`destroying texture ${t} for tile ${tile.key}`);
                            texture.destroy();
                        }
                    }
                }
            }
        }
    }

    printDebug () {
        log.debug(`Tile: debug for ${this.key}: [  ${JSON.stringify(this.debug)} ]`);
    }

    update() {
        let scene = this.tile_manager.scene;
        let coords = this.coords;
        if (coords.z !== scene.center_tile.z) {
            coords = Tile.coordinateAtZoom(coords, scene.center_tile.z);
        }
        this.center_dist = Math.abs(scene.center_tile.x - coords.x) + Math.abs(scene.center_tile.y - coords.y);
    }

    // Slice a subset of keys out of a tile
    // Includes a minimum set of pre-defined keys for load state, debug. etc.
    // We use this to send a subset of the tile back to the main thread, to minimize unnecessary data transfer
    // (e.g. very large items like feature geometry are not needed on the main thread)
    static slice (tile, keys) {
        let keep = [
            'key',
            'loading',
            'loaded',
            'generation',
            'error',
            'debug'
        ];
        if (Array.isArray(keys)) {
            keep.push(...keys);
        }

        // Build the tile subset
        var tile_subset = {};
        for (let key of keep) {
            tile_subset[key] = tile[key];
        }

        return tile_subset;
    }

    /**
        Called on worker to cancel loading
        Static method because the worker only has object representations of tile data, there is no
        tile instance created yet.
    */
    static cancel(tile) {
        if (tile) {
            if (tile.source_data && tile.source_data.request) {
                tile.source_data.request.abort();
            }
            Tile.abortBuild(tile);
        }
    }

    merge(other) {
        for (var key in other) {
            if (key !== 'key') {
                this[key] = other[key];
            }
        }
        return this;
    }

}
<|endoftext|>