<commit_before>var workQueue = [];
/**
 * Competence class
 */
function Competence(viewmodel, id, code, name, weight, locked, subcompetences) {
    return {
        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        weight: ko.observable(weight),
        locked: false,
        subcompetences: ko.observableArray(subcompetences),

        addSubCompetence: function() {
            this.subcompetences.push(new SubCompetence(this));
            automatedWeightCalculation(this.subcompetences());
        },

        removeThis: function() {
            viewmodel.removeCompetence(this);
        },

        toggleLock: function(data, event){
            if(this.locked == true)
            {
                $(event.target).addClass("icon-unlock").removeClass("icon-lock");
                this.locked = false;
            }
            else
            {
                $(event.target).addClass("icon-lock").removeClass("icon-unlock");
                this.locked = true;
            }
        },

        removeSubCompetence: function(subCompetence) {
            if(subCompetence.id() !== undefined) // User deletes a subcompetence that hasn't been saved to the database yet
            {
                workQueue.push("/api/project/subcompetence/delete/" + subCompetence.id())
            }

            this.subcompetences.remove(subCompetence);
            automatedWeightCalculation(this.subcompetences());
        }
    };

}

/**
 * SubCompetence class
 */
function SubCompetence(parent, id, code, name, weight, locked, indicators) {
    return {

        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        weight: ko.observable(weight),
        locked: false,
        indicators: ko.observableArray(indicators),

        addIndicator: function() {
            this.indicators.push(new Indicator(this));
            automatedWeightCalculation(this.indicators());
        },

        /*calculateWeight: function(total){
            this.weight = total;
        },*/

        removeThis: function() {
            parent.removeSubCompetence(this);

        },

        toggleLock: function(data, event){
            if(this.locked == true)
            {
                $(event.target).addClass("icon-unlock").removeClass("icon-lock");
                this.locked = false;
            }
            else
            {
                $(event.target).addClass("icon-lock").removeClass("icon-unlock");
                this.locked = true;
            }
        },

        removeIndicator: function(indicator) {
            var self = this;

            if(indicator.id() != undefined) // User deletes an indicator that hasn't been saved to the database yet
            {
                workQueue.push("/api/project/indicator/delete/" + indicator.id())
            }

            self.indicators.remove(indicator);
            automatedWeightCalculation(this.indicators());
        }

    };
}

/**
 * Indicator class
 */
function Indicator(parent, id, name, weight, description, locked) {
    return {
        id: ko.observable(id),
        name: ko.observable(name),
        weight: ko.observable(weight),
        description : ko.observable(description),
        locked: false,

        removeThis: function() {
            parent.removeIndicator(this);
        },

        toggleLock: function(data, event){
            if(this.locked == true)
            {
                $(event.target).addClass("icon-unlock").removeClass("icon-lock");
                this.locked = false;
            }
            else
            {
                $(event.target).addClass("icon-lock").removeClass("icon-unlock");
                this.locked = true;
            }
        }
    };
}

// View model for the courses page
function pageViewModel(gvm) {
    // projecttitle
    gvm.projecttitle = ko.observable("");
    //gvm.competences = 0;
    gvm.subcomp = 0;

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.addCompetenceBtn = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("AddCompetence");}, gvm);
    gvm.savePage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("SaveBtn");}, gvm);

    gvm.competences = ko.observableArray([]);

    gvm.addCompetence = function() {
        gvm.competences.push(new Competence(this));
        automatedWeightCalculation(this.competences());

        // Update automated weight calculation
        ko.utils.arrayForEach(gvm.competences, function(competence){
            console.log(competence.weight);
            competence.weight(percent);
        });
    };

    gvm.removeCompetence = function(competence) {
        gvm.competences.remove(competence);
        automatedWeightCalculation(this.competences());
    }

    gvm.updateCompetence = function(id, code, description, max, weight) {
        var comp = new Competence(this, id, code, description, weight);
        gvm.competences.push(comp);
        return comp;
    }

    gvm.clearStructure = function() {
        gvm.competences.destroyAll();
    }
}

function fetchProjectStructure() {
    viewModel.clearStructure();
    console.log(projectid);
    $.getJSON("/api/projectstructure/" + projectid, function(data){
        console.log(data);
        $.each(data, function(i, item){
            var competence = viewModel.updateCompetence(item.id, item.code, item.description, item.max, item.weight);

            $.each(item.subcompetences, function(i, subcomp){
                if(subcomp.id > 0) {
                    var subcompetence = new SubCompetence(competence, subcomp.id, subcomp.code, subcomp.description, subcomp.weight);
                    competence.subcompetences.push(subcompetence);
                }

               $.each(subcomp.indicators, function(i, indic){
                   if(indic.id > 0) {
                       subcompetence.indicators.push(new Indicator(subcompetence, indic.id, indic.name, indic.weight, indic.description));
                   }
               });
            });
        })
    });
}

function initPage() {
    fetchProjectStructure();

    $(".addCompetenceBtn").click(function() {
        viewModel.addCompetence();
    });

    $(".savePageBtn").click(function(){
        if(allValidationChecks()) {
            saveProjectStructure();
            console.log("Saved");
        }
        else
        {
            window.scrollTo(0,0);
            console.log("Not saved");
        }
    });
}

function saveProjectStructure() {
    var self = this;

    this.doTheRest = function()
    {
        $.ajax({
            type: "POST",
            url: "/api/projectstructure/" + projectid,
            data: ko.toJSON(viewModel.competences),
            success: function(){
                // TODO make multilangual and with modals
                alert("Saved projectstructure to server");

                fetchProjectStructure();
            }
        });
    }

    if(workQueue.length === 0)
    {
        this.doTheRest();
    }

    var processedIndex = 0;
    for(i = 0; i < workQueue.length; i++)
    {
        $.getJSON(
            workQueue[i],
            function(result)
            {
                if(!result.success)
                {
                    alert("Failed processing your request: " + result.error);
                }

                if(processedIndex === workQueue.length - 1)
                {
                    self.doTheRest();
                    workQueue = [];
                }
                processedIndex++;
            }
        );
    }

}

function allValidationChecks()
{
    return totalPercentCheck() && validationCheck();
}

function validationCheck()
{
    var allCompetencesValid = true;
    var allSubcompetencesValid = true;
    var allIndicatorsValid = true;

    for(var indexCompetences =0; indexCompetences < viewModel.competences().length; indexCompetences++)
    {
        if(!viewModel.competences()[indexCompetences].code() && !viewModel.competences()[indexCompetences].name())
        {
            if(allCompetencesValid)
            {
                $(".validationSummary ul").append("
Code or name in competences is empty
");
                $(".validationSummary").removeClass("hide");
            }
            allCompetencesValid = false;
        }
        for(var indexSubcompetence = 0; indexSubcompetence < viewModel.competences()[indexCompetences].subcompetences().length; indexSubcompetence++)
        {
            if(!viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].name() && !viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].code())
            {
                if(allSubcompetencesValid)
                {
                    $(".validationSummary ul").append("
Code or name in subcompetences is empty
");
                    $(".validationSummary").removeClass("hide");
                }
                allSubcompetencesValid = false;
            }
            for(var indexIndicators = 0; indexIndicators < viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators().length; indexIndicators++)
            {
                if(!viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators()[indexIndicators].name() && !viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators()[indexIndicators].description())
                {
                    if(allIndicatorsValid)
                    {
                        $(".validationSummary ul").append("
Description or name in indicators is empty
");
                        $(".validationSummary").removeClass("hide");
                    }
                    allIndicatorsValid = false;
                }
            }
        }
    }
    return allCompetencesValid && allSubcompetencesValid && allIndicatorsValid;
}

function totalPercentCheck()
{
    var totalPercentCompetences = 0;
    var totalPercentSubcompetences = new Array();
    var totalPercentIndicators = new Array();
    var nrOfSubcompetences = 0;
    var nrOfIndicators = 0;

    var checkSubcompetences = true;
    var checkIndicators = true;

    for(var indexCompetences =0; indexCompetences < viewModel.competences().length; indexCompetences++)
    {
        totalPercentCompetences = totalPercentCompetences + parseInt(viewModel.competences()[indexCompetences].weight());
        totalPercentSubcompetences.push(0);
        for(var indexSubcompetence = 0; indexSubcompetence < viewModel.competences()[indexCompetences].subcompetences().length; indexSubcompetence++)
        {
            totalPercentSubcompetences[nrOfSubcompetences] = totalPercentSubcompetences[nrOfSubcompetences] + parseInt(viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].weight());
            totalPercentIndicators.push(0);
            for(var indexIndicators = 0; indexIndicators < viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators().length; indexIndicators++)
            {
                totalPercentIndicators[nrOfIndicators] = totalPercentIndicators[nrOfIndicators] + parseInt(viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators()[indexIndicators].weight());
            }
            nrOfIndicators++;
        }
        nrOfSubcompetences++;
    }

    for(var i = 0; i < nrOfSubcompetences - 1; i++)
    {
        if(totalPercentSubcompetences[i] != 100)
        {
            checkSubcompetences = false;
            console.log(totalPercentSubcompetences);
            console.log('Check subcomp failed');
        }
    }

    for(var index = 0; index < nrOfIndicators - 1; index++)
    {
        if(totalPercentIndicators[index] != 100)
        {
            checkIndicators = false;
            console.log(nrOfIndicators);
            console.log('Check indicators failed');
        }
    }

    if(totalPercentCompetences == 100 && checkSubcompetences && checkIndicators )
    {

        $(".validationSummary ul").html("");
        $(".validationSummary").addClass("hide");
        return true;
    }
    else
    {
        console.log(totalPercentCompetences);
        $(".validationSummary ul").append("
Not all percentages are 100%
");
        $(".validationSummary").removeClass("hide");
        return false;
    }
}

function automatedWeightCalculation(data)
{
    var lockedPercent = 0;
    var nrOfUnlocked = 0;

    for(var index = 0; index < data.length; index++)
    {
        if(data[index].locked == true)
        {
            lockedPercent = lockedPercent + parseInt(data[index].weight());
        }
        else
        {
            nrOfUnlocked++;
        }
    }

    var remainingPercent = 100 - lockedPercent;

    var percentPerCompetence = remainingPercent / nrOfUnlocked;

    percentPerCompetence = percentPerCompetence.toFixed(2);

    for(var index = 0; index < data.length; index++)
    {
        if(data[index].locked == false)
        {
            data[index].weight(percentPerCompetence);
        }
    }
}
<commit_msg>bug fix<commit_after>var workQueue = [];
/**
 * Competence class
 */
function Competence(viewmodel, id, code, name, weight, locked, subcompetences) {
    return {
        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        weight: ko.observable(weight),
        locked: false,
        subcompetences: ko.observableArray(subcompetences),

        addSubCompetence: function() {
            this.subcompetences.push(new SubCompetence(this));
            automatedWeightCalculation(this.subcompetences());
        },

        removeThis: function() {
            viewmodel.removeCompetence(this);
        },

        toggleLock: function(data, event){
            if(this.locked == true)
            {
                $(event.target).addClass("icon-unlock").removeClass("icon-lock");
                this.locked = false;
            }
            else
            {
                $(event.target).addClass("icon-lock").removeClass("icon-unlock");
                this.locked = true;
            }
        },

        removeSubCompetence: function(subCompetence) {
            if(subCompetence.id() !== undefined) // User deletes a subcompetence that hasn't been saved to the database yet
            {
                workQueue.push("/api/project/subcompetence/delete/" + subCompetence.id())
            }

            this.subcompetences.remove(subCompetence);
            automatedWeightCalculation(this.subcompetences());
        }
    };

}

/**
 * SubCompetence class
 */
function SubCompetence(parent, id, code, name, weight, locked, indicators) {
    return {

        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        weight: ko.observable(weight),
        locked: false,
        indicators: ko.observableArray(indicators),

        addIndicator: function() {
            this.indicators.push(new Indicator(this));
            automatedWeightCalculation(this.indicators());
        },

        /*calculateWeight: function(total){
            this.weight = total;
        },*/

        removeThis: function() {
            parent.removeSubCompetence(this);

        },

        toggleLock: function(data, event){
            if(this.locked == true)
            {
                $(event.target).addClass("icon-unlock").removeClass("icon-lock");
                this.locked = false;
            }
            else
            {
                $(event.target).addClass("icon-lock").removeClass("icon-unlock");
                this.locked = true;
            }
        },

        removeIndicator: function(indicator) {
            var self = this;

            if(indicator.id() != undefined) // User deletes an indicator that hasn't been saved to the database yet
            {
                workQueue.push("/api/project/indicator/delete/" + indicator.id())
            }

            self.indicators.remove(indicator);
            automatedWeightCalculation(this.indicators());
        }

    };
}

/**
 * Indicator class
 */
function Indicator(parent, id, name, weight, description, locked) {
    return {
        id: ko.observable(id),
        name: ko.observable(name),
        weight: ko.observable(weight),
        description : ko.observable(description),
        locked: false,

        removeThis: function() {
            parent.removeIndicator(this);
        },

        toggleLock: function(data, event){
            if(this.locked == true)
            {
                $(event.target).addClass("icon-unlock").removeClass("icon-lock");
                this.locked = false;
            }
            else
            {
                $(event.target).addClass("icon-lock").removeClass("icon-unlock");
                this.locked = true;
            }
        }
    };
}

// View model for the courses page
function pageViewModel(gvm) {
    // projecttitle
    gvm.projecttitle = ko.observable("");
    //gvm.competences = 0;
    gvm.subcomp = 0;

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.addCompetenceBtn = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("AddCompetence");}, gvm);
    gvm.savePage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("SaveBtn");}, gvm);

    gvm.competences = ko.observableArray([]);

    gvm.addCompetence = function() {
        gvm.competences.push(new Competence(this));
        automatedWeightCalculation(this.competences());

        // Update automated weight calculation
        ko.utils.arrayForEach(gvm.competences, function(competence){
            console.log(competence.weight);
            competence.weight(percent);
        });
    };

    gvm.removeCompetence = function(competence) {
        gvm.competences.remove(competence);
        automatedWeightCalculation(this.competences());
    }

    gvm.updateCompetence = function(id, code, description, max, weight) {
        var comp = new Competence(this, id, code, description, weight);
        gvm.competences.push(comp);
        return comp;
    }

    gvm.clearStructure = function() {
        gvm.competences.destroyAll();
    }
}

function fetchProjectStructure() {
    viewModel.clearStructure();
    console.log(projectid);
    $.getJSON("/api/projectstructure/" + projectid, function(data){
        console.log(data);
        $.each(data, function(i, item){
            if(item.id > 0) {
                var competence = viewModel.updateCompetence(item.id, item.code, item.description, item.max, item.weight);
            }
            $.each(item.subcompetences, function(i, subcomp){
                if(subcomp.id > 0) {
                    var subcompetence = new SubCompetence(competence, subcomp.id, subcomp.code, subcomp.description, subcomp.weight);
                    competence.subcompetences.push(subcompetence);
                }

               $.each(subcomp.indicators, function(i, indic){
                   if(indic.id > 0) {
                       subcompetence.indicators.push(new Indicator(subcompetence, indic.id, indic.name, indic.weight, indic.description));
                   }
               });
            });
        })
    });
}

function initPage() {
    fetchProjectStructure();

    $(".addCompetenceBtn").click(function() {
        viewModel.addCompetence();
    });

    $(".savePageBtn").click(function(){
        if(allValidationChecks()) {
            saveProjectStructure();
            console.log("Saved");
        }
        else
        {
            window.scrollTo(0,0);
            console.log("Not saved");
        }
    });
}

function saveProjectStructure() {
    var self = this;

    this.doTheRest = function()
    {
        $.ajax({
            type: "POST",
            url: "/api/projectstructure/" + projectid,
            data: ko.toJSON(viewModel.competences),
            success: function(){
                // TODO make multilangual and with modals
                alert("Saved projectstructure to server");

                fetchProjectStructure();
            }
        });
    }

    if(workQueue.length === 0)
    {
        this.doTheRest();
    }

    var processedIndex = 0;
    for(i = 0; i < workQueue.length; i++)
    {
        $.getJSON(
            workQueue[i],
            function(result)
            {
                if(!result.success)
                {
                    alert("Failed processing your request: " + result.error);
                }

                if(processedIndex === workQueue.length - 1)
                {
                    self.doTheRest();
                    workQueue = [];
                }
                processedIndex++;
            }
        );
    }

}

function allValidationChecks()
{
    return totalPercentCheck() && validationCheck();
}

function validationCheck()
{
    var allCompetencesValid = true;
    var allSubcompetencesValid = true;
    var allIndicatorsValid = true;

    for(var indexCompetences =0; indexCompetences < viewModel.competences().length; indexCompetences++)
    {
        if(!viewModel.competences()[indexCompetences].code() && !viewModel.competences()[indexCompetences].name())
        {
            if(allCompetencesValid)
            {
                $(".validationSummary ul").append("
Code or name in competences is empty
");
                $(".validationSummary").removeClass("hide");
            }
            allCompetencesValid = false;
        }
        for(var indexSubcompetence = 0; indexSubcompetence < viewModel.competences()[indexCompetences].subcompetences().length; indexSubcompetence++)
        {
            if(!viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].name() && !viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].code())
            {
                if(allSubcompetencesValid)
                {
                    $(".validationSummary ul").append("
Code or name in subcompetences is empty
");
                    $(".validationSummary").removeClass("hide");
                }
                allSubcompetencesValid = false;
            }
            for(var indexIndicators = 0; indexIndicators < viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators().length; indexIndicators++)
            {
                if(!viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators()[indexIndicators].name() && !viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators()[indexIndicators].description())
                {
                    if(allIndicatorsValid)
                    {
                        $(".validationSummary ul").append("
Description or name in indicators is empty
");
                        $(".validationSummary").removeClass("hide");
                    }
                    allIndicatorsValid = false;
                }
            }
        }
    }
    return allCompetencesValid && allSubcompetencesValid && allIndicatorsValid;
}

function totalPercentCheck()
{
    var totalPercentCompetences = 0;
    var totalPercentSubcompetences = new Array();
    var totalPercentIndicators = new Array();
    var nrOfSubcompetences = 0;
    var nrOfIndicators = 0;

    var checkSubcompetences = true;
    var checkIndicators = true;

    for(var indexCompetences =0; indexCompetences < viewModel.competences().length; indexCompetences++)
    {
        totalPercentCompetences = totalPercentCompetences + parseInt(viewModel.competences()[indexCompetences].weight());
        totalPercentSubcompetences.push(0);
        for(var indexSubcompetence = 0; indexSubcompetence < viewModel.competences()[indexCompetences].subcompetences().length; indexSubcompetence++)
        {
            totalPercentSubcompetences[nrOfSubcompetences] = totalPercentSubcompetences[nrOfSubcompetences] + parseInt(viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].weight());
            totalPercentIndicators.push(0);
            for(var indexIndicators = 0; indexIndicators < viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators().length; indexIndicators++)
            {
                totalPercentIndicators[nrOfIndicators] = totalPercentIndicators[nrOfIndicators] + parseInt(viewModel.competences()[indexCompetences].subcompetences()[indexSubcompetence].indicators()[indexIndicators].weight());
            }
            nrOfIndicators++;
        }
        nrOfSubcompetences++;
    }

    for(var i = 0; i < nrOfSubcompetences - 1; i++)
    {
        if(totalPercentSubcompetences[i] != 100)
        {
            checkSubcompetences = false;
            console.log(totalPercentSubcompetences);
            console.log('Check subcomp failed');
        }
    }

    for(var index = 0; index < nrOfIndicators - 1; index++)
    {
        if(totalPercentIndicators[index] != 100)
        {
            checkIndicators = false;
            console.log(nrOfIndicators);
            console.log('Check indicators failed');
        }
    }

    if(totalPercentCompetences == 100 && checkSubcompetences && checkIndicators )
    {
        $(".validationSummary ul").html("");
        $(".validationSummary").addClass("hide");
        return true;
    }
    else
    {
        console.log(totalPercentCompetences);
        $(".validationSummary ul").append("
Not all percentages are 100%
");
        $(".validationSummary").removeClass("hide");
        return false;
    }
}

function automatedWeightCalculation(data)
{
    var lockedPercent = 0;
    var nrOfUnlocked = 0;

    for(var index = 0; index < data.length; index++)
    {
        if(data[index].locked == true)
        {
            lockedPercent = lockedPercent + parseInt(data[index].weight());
        }
        else
        {
            nrOfUnlocked++;
        }
    }

    var remainingPercent = 100 - lockedPercent;

    var percentPerCompetence = remainingPercent / nrOfUnlocked;

    percentPerCompetence = percentPerCompetence.toFixed(2);

    for(var index = 0; index < data.length; index++)
    {
        if(data[index].locked == false)
        {
            data[index].weight(percentPerCompetence);
        }
    }
}
<|endoftext|>


<commit_before>// Imported Electron modules
import Path from 'path'
import loadingScreen from './loadingScreen.js'
import Electron from 'electron'
import { scanFolder, loadPlugin, setCurrentPlugin, getPluginName } from './plugins.js'

const App = Electron.remote.app
const Tray = Electron.remote.Tray
const mainWindow = Electron.remote.getCurrentWindow()
const defaultPluginDirectory = Path.join(App.getAppPath(), './plugins')
const defaultHomePlugin = 'Files'

// Called at window.onload by the loading screen.
// Wait for siad to load, then load the plugin system.
function init(callback) {
	// Initialize plugins
	let plugins = scanFolder(defaultPluginDirectory)

	// The home plugin should be first in the sidebar, and about should be last.
	// We probably want a priority system for this instead.
	plugins = plugins.sort((p1) => {
		if (getPluginName(p1) === 'About') {
			return 1
		}
		return 0
	})

	plugins = plugins.sort((p1, p2) => {
		if (getPluginName(p2) === defaultHomePlugin) {
			return 1
		}
		return 0
	})

	let homePluginView
	// Load each plugin element into the UI
	for (let i = 0; i < plugins.size; i++) {
		const plugin = loadPlugin(plugins.get(i))
		if (getPluginName(plugins.get(i)) === defaultHomePlugin) {
			homePluginView = plugin
		}
	}
	const onHomeLoad = () => {
		setCurrentPlugin(defaultHomePlugin)
		homePluginView.removeEventListener('dom-ready', onHomeLoad)
		callback()
	}
	// wait for the home plugin to load before calling back
	homePluginView.addEventListener('dom-ready', onHomeLoad)
}
// If closeToTray is set, hide the window and cancel the close.
// On windows, display a baloon notification on first hide
// to inform users that Sia-UI is still running.
let hasClosed = false
if (mainWindow.closeToTray) {
	window.onbeforeunload = function() {
		mainWindow.hide()
		if (process.platform === 'win32' && !hasClosed) {
			Tray.displayBalloon({
				title: 'Sia-UI information',
				content: 'Sia is still running.  Right click this tray icon to quit or restore Sia.',
			})
			hasClosed = true
		}
		return false
	}
}

// Once the main window loads, start the loading process.
window.onload = function() {
	loadingScreen(init)
}
<commit_msg>fix typo<commit_after>// Imported Electron modules
import Path from 'path'
import loadingScreen from './loadingScreen.js'
import Electron from 'electron'
import { scanFolder, loadPlugin, setCurrentPlugin, getPluginName } from './plugins.js'

const App = Electron.remote.app
const Tray = Electron.remote.Tray
const mainWindow = Electron.remote.getCurrentWindow()
const defaultPluginDirectory = Path.join(App.getAppPath(), './plugins')
const defaultHomePlugin = 'Files'

// Called at window.onload by the loading screen.
// Wait for siad to load, then load the plugin system.
function init(callback) {
	// Initialize plugins
	let plugins = scanFolder(defaultPluginDirectory)

	// The home plugin should be first in the sidebar, and about should be last.
	// We probably want a priority system for this instead.
	plugins = plugins.sort((p1) => {
		if (getPluginName(p1) === 'About') {
			return 1
		}
		return 0
	})

	plugins = plugins.sort((p1, p2) => {
		if (getPluginName(p2) === defaultHomePlugin) {
			return 1
		}
		return 0
	})

	let homePluginView
	// Load each plugin element into the UI
	for (let i = 0; i < plugins.size; i++) {
		const plugin = loadPlugin(plugins.get(i))
		if (getPluginName(plugins.get(i)) === defaultHomePlugin) {
			homePluginView = plugin
		}
	}
	const onHomeLoad = () => {
		setCurrentPlugin(defaultHomePlugin)
		homePluginView.removeEventListener('dom-ready', onHomeLoad)
		callback()
	}
	// wait for the home plugin to load before calling back
	homePluginView.addEventListener('dom-ready', onHomeLoad)
}
// If closeToTray is set, hide the window and cancel the close.
// On windows, display a balloon notification on first hide
// to inform users that Sia-UI is still running.
let hasClosed = false
if (mainWindow.closeToTray) {
	window.onbeforeunload = function() {
		mainWindow.hide()
		if (process.platform === 'win32' && !hasClosed) {
			Tray.displayBalloon({
				title: 'Sia-UI information',
				content: 'Sia is still running.  Right click this tray icon to quit or restore Sia.',
			})
			hasClosed = true
		}
		return false
	}
}

// Once the main window loads, start the loading process.
window.onload = function() {
	loadingScreen(init)
}
<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrew Adare (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var assertInstanceOf = require( 'PHET_IO/assertions/assertInstanceOf' );
  var phetio = require( 'PHET_IO/phetio' );
  var phetioInherit = require( 'PHET_IO/phetioInherit' );
  var phetioNamespace = require( 'PHET_IO/phetioNamespace' );
  var SimIFrameAPI = require( 'PHET_IO/SimIFrameAPI' );
  var TFunctionWrapper = require( 'PHET_IO/types/TFunctionWrapper' );
  var TObject = require( 'PHET_IO/types/TObject' );
  var toEventOnEmit = require( 'PHET_IO/events/toEventOnEmit' );
  var TString = require( 'PHET_IO/types/TString' );
  var TVoid = require( 'PHET_IO/types/TVoid' );

  /**
   * Wrapper type for phet/joist's Sim class.
   * @param sim
   * @param phetioID
   * @constructor
   */
  function TSim( sim, phetioID ) {
    TObject.call( this, sim, phetioID );
    assertInstanceOf( sim, phet.joist.Sim );

    // startedSimConstructorEmitter is called in the constructor of the sim, and endedSimConstructionEmitter is called
    // once all of the screens have been fully initialized, hence construction not constructor.
    toEventOnEmit( sim.startedSimConstructorEmitter, sim.endedSimConstructionEmitter, 'model', phetioID, TSim, 'simStarted',
      function( value ) {
        var simStartedData = {
          sessionID: value.sessionID,
          repoName: value.repoName,
          simName: value.simName,
          simVersion: value.simVersion,
          url: value.url,
          referrer: document.referrer,
          userAgent: window.navigator.userAgent,
          randomSeed: value.randomSeed,
          provider: 'PhET Interactive Simulations, University of Colorado Boulder' // See #137
        };
        if ( window.simStartedMetadata ) {
          simStartedData.extraSimStartedMetadata = window.simStartedMetadata;
        }
        return simStartedData;
      } );

    // Store a reference to the sim so that subsequent calls will be simpler.  PhET-iO only works with a single sim.
    phetio.sim = sim;
    sim.endedSimConstructionEmitter.addListener( function() {

      // TODO: Can these be coalesced?
      SimIFrameAPI.triggerSimInitialized();
      phetio.simulationStarted();
    } );
  }

  phetioInherit( TObject, 'TSim', TSim, {

    disableRequestAnimationFrame: {
      returnType: TVoid,
      parameterTypes: [],
      implementation: function() {
        this.instance.disableRequestAnimationFrame();
      },
      documentation: 'Prevents the simulation from animating/updating'
    },

    addEventListener: {
      returnType: TVoid,
      parameterTypes: [ TString, TFunctionWrapper( TVoid, [ TString, TFunctionWrapper( TVoid, [] ) ] ) ],
      implementation: function( eventName, listener ) {
        this.instance.onStatic( eventName, listener );
      },
      documentation: 'Add an event listener to the sim instance'
    },

    getScreenshotDataURL: {
      returnType: TString,
      parameterTypes: [],
      implementation: function() {
        return window.phet.joist.ScreenshotGenerator.generateScreenshot( this.instance );
      },
      documentation: 'Gets a base64 representation of a screenshot of the simulation as a data url'
    }
  }, {
    documentation: 'The type for the simulation instance',
    events: [
      'simStarted'
    ]
  } );

  phetioNamespace.register( 'TSim', TSim );

  return TSim;
} );

<commit_msg>rename and update data that is logged, https://github.com/phetsims/phet-io/issues/934<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrew Adare (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var assertInstanceOf = require( 'PHET_IO/assertions/assertInstanceOf' );
  var phetio = require( 'PHET_IO/phetio' );
  var phetioInherit = require( 'PHET_IO/phetioInherit' );
  var phetioNamespace = require( 'PHET_IO/phetioNamespace' );
  var SimIFrameAPI = require( 'PHET_IO/SimIFrameAPI' );
  var TFunctionWrapper = require( 'PHET_IO/types/TFunctionWrapper' );
  var TObject = require( 'PHET_IO/types/TObject' );
  var toEventOnEmit = require( 'PHET_IO/events/toEventOnEmit' );
  var TString = require( 'PHET_IO/types/TString' );
  var TVoid = require( 'PHET_IO/types/TVoid' );

  /**
   * Wrapper type for phet/joist's Sim class.
   * @param sim
   * @param phetioID
   * @constructor
   */
  function TSim( sim, phetioID ) {
    TObject.call( this, sim, phetioID );
    assertInstanceOf( sim, phet.joist.Sim );

    // startedSimConstructorEmitter is called in the constructor of the sim, and endedSimConstructionEmitter is called
    // once all of the screens have been fully initialized, hence construction not constructor.
    toEventOnEmit( sim.startedSimConstructorEmitter, sim.endedSimConstructionEmitter, 'model', phetioID, TSim, 'simStarted',
      function( value ) {
        return {
          sessionID: value.sessionID,
          repoName: value.repoName,
          simName: value.simName,
          simVersion: value.simVersion,
          simURL: value.url,
          userAgent: window.navigator.userAgent,
          randomSeed: value.randomSeed,
          extraMetadata: window.simStartedMetadata,
          provider: 'PhET Interactive Simulations, University of Colorado Boulder' // See #137
        };
      } );

    // Store a reference to the sim so that subsequent calls will be simpler.  PhET-iO only works with a single sim.
    phetio.sim = sim;
    sim.endedSimConstructionEmitter.addListener( function() {

      // TODO: Can these be coalesced?
      SimIFrameAPI.triggerSimInitialized();
      phetio.simulationStarted();
    } );
  }

  phetioInherit( TObject, 'TSim', TSim, {

    disableRequestAnimationFrame: {
      returnType: TVoid,
      parameterTypes: [],
      implementation: function() {
        this.instance.disableRequestAnimationFrame();
      },
      documentation: 'Prevents the simulation from animating/updating'
    },

    addEventListener: {
      returnType: TVoid,
      parameterTypes: [ TString, TFunctionWrapper( TVoid, [ TString, TFunctionWrapper( TVoid, [] ) ] ) ],
      implementation: function( eventName, listener ) {
        this.instance.onStatic( eventName, listener );
      },
      documentation: 'Add an event listener to the sim instance'
    },

    getScreenshotDataURL: {
      returnType: TString,
      parameterTypes: [],
      implementation: function() {
        return window.phet.joist.ScreenshotGenerator.generateScreenshot( this.instance );
      },
      documentation: 'Gets a base64 representation of a screenshot of the simulation as a data url'
    }
  }, {
    documentation: 'The type for the simulation instance',
    events: [
      'simStarted'
    ]
  } );

  phetioNamespace.register( 'TSim', TSim );

  return TSim;
} );

<|endoftext|>


<commit_before>// Copyright 2015-2016, University of Colorado Boulder

/**
 * A single Canvas/texture with multiple different images (sprites) drawn internally. During rendering, this texture
 * can be used in one draw call to render multiple different images by providing UV coordinates to each quad for each
 * image to be drawn.
 *
 * Note that the WebGL texture part is not required to be run - the Canvas-only part can be used functionally without
 * any WebGL dependencies.
 *
 * TODO: Add padding around sprites, otherwise interpolation could cause issues!
 * TODO: How to use custom mipmap levels?
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var scenery = require( 'SCENERY/scenery' );
  var inherit = require( 'PHET_CORE/inherit' );
  var BinPacker = require( 'DOT/BinPacker' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Dimension2 = require( 'DOT/Dimension2' );

  // constants
  // The max SpriteSheet size was selected to minimize memory overhead while still accommodating many large images
  // See https://github.com/phetsims/scenery/issues/539
  var MAX_DIMENSION = new Dimension2( 1024, 1024 );

  /**
   * @constructor
   *
   * @param {boolean} useMipmaps - Whether built-in WebGL mipmapping should be used. Higher quality, but may be slower
   *                               to add images (since mipmaps need to be updated).
   */
  function SpriteSheet( useMipmaps ) {
    this.useMipmaps = useMipmaps;

    this.gl = null; // assume later creation of context for now

    // Use the max supported texture size (according to http://codeflow.org/entries/2013/feb/22/how-to-write-portable-webgl/ )
    // TODO: potentially support larger texture sizes based on reported capabilities (could cause fewer draw calls?)
    this.bounds = new Bounds2( 0, 0, MAX_DIMENSION.width, MAX_DIMENSION.height );
    assert && assert( this.bounds.minX === 0 && this.bounds.minY === 0, 'Assumed constraint later on for transforms' );
    this.width = this.bounds.width;
    this.height = this.bounds.height;

    this.canvas = document.createElement( 'canvas' );
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    this.context = this.canvas.getContext( '2d' );

    this.binPacker = new BinPacker( this.bounds );

    this.dirty = true; // @public [read-only] - Used to check if we need to updateTexture()

    this.usedSprites = [];
    this.unusedSprites = []; // works as a LRU cache for removing items when we need to allocate new space
  }

  scenery.register( 'SpriteSheet', SpriteSheet );

  inherit( Object, SpriteSheet, {
    /**
     * Initialize (or reinitialize) ourself with a new GL context. Should be called at least once before updateTexture()
     */
    initializeContext: function( gl ) {
      this.gl = gl;

      this.createTexture();
    },

    /**
     * Allocates and creates a GL texture, configures it, and initializes it with our current Canvas.
     * @private
     */
    createTexture: function() {
      var gl = this.gl;

      this.texture = gl.createTexture();
      gl.bindTexture( gl.TEXTURE_2D, this.texture );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.useMipmaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
      gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
      gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas );
      if ( this.useMipmaps ) {
        gl.hint( gl.GENERATE_MIPMAP_HINT, gl.NICEST );
        gl.generateMipmap( gl.TEXTURE_2D );
      }
      gl.bindTexture( gl.TEXTURE_2D, null );

      this.dirty = false;
    },

    /**
     * Updates a pre-existing texture with our current Canvas.
     */
    updateTexture: function() {
      assert && assert( this.gl, 'SpriteSheet needs context to updateTexture()' );

      if ( this.dirty ) {
        this.dirty = false;

        var gl = this.gl;

        gl.bindTexture( gl.TEXTURE_2D, this.texture );
        gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas );
        if ( this.useMipmaps ) {
          gl.generateMipmap( gl.TEXTURE_2D );
        }
        gl.bindTexture( gl.TEXTURE_2D, null );
      }
    },

    /**
     * Adds an image (if possible) to our sprite sheet. If successful, will return a {Sprite}, otherwise null.
     *
     * @param {HTMLCanvasElement | HTMLImageElement} image
     * @param {number} width
     * @param {number} height
     *
     * @returns {Sprite | null}
     */
    addImage: function( image, width, height ) {
      var i;

      // check used cache
      for ( i = 0; i < this.usedSprites.length; i++ ) {
        var usedSprite = this.usedSprites[ i ];
        if ( usedSprite.image === image ) {
          usedSprite.count++;
          return usedSprite;
        }
      }

      // check unused cache
      for ( i = 0; i < this.unusedSprites.length; i++ ) {
        var unusedSprite = this.unusedSprites[ i ];
        if ( unusedSprite.image === image ) {
          unusedSprite.count++;
          assert && assert( unusedSprite.count === 1, 'Count should be exactly 1 after coming back from being unused' );
          this.unusedSprites.splice( i, 1 ); // remove it from the unused array
          this.usedSprites.push( unusedSprite ); // add it to the used array
          return unusedSprite;
        }
      }

      // Not in any caches, let's try to find space. If we can't find space at first, we start removing unused sprites
      // one-by-one.
      var bin;
      // Enters 'while' loop only if allocate() returns null and we have unused sprites (i.e. conditions where we will
      // want to deallocate the least recently used (LRU) unused sprite and then check for allocation again).
      while ( !( bin = this.binPacker.allocate( width, height ) ) && this.unusedSprites.length ) {
        var ejectedSprite = this.unusedSprites.shift(); // LRU policy by taking first item

        // clear its space in the Canvas
        this.dirty = true;
        var ejectedBounds = ejectedSprite.bin.bounds;
        this.context.clearRect( ejectedBounds.x, ejectedBounds.y, ejectedBounds.width, ejectedBounds.height );

        // deallocate its area in the bin packer
        this.binPacker.deallocate( ejectedSprite.bin );
      }

      if ( bin ) {
        // WebGL will want UV coordinates in the [0,1] range
        var uvBounds = new Bounds2( bin.bounds.minX / this.width, bin.bounds.minY / this.height,
          bin.bounds.maxX / this.width, bin.bounds.maxY / this.height );
        var sprite = new SpriteSheet.Sprite( this, bin, uvBounds, image, 1 );
        this.context.drawImage( image, bin.bounds.x, bin.bounds.y );
        this.dirty = true;
        this.usedSprites.push( sprite );
        return sprite;
      }
      // no space, even after clearing out our unused sprites
      else {
        return null;
      }
    },

    removeImage: function( image ) {
      // find the used sprite (and its index)
      var usedSprite;
      var i;
      for ( i = 0; i < this.usedSprites.length; i++ ) {
        if ( this.usedSprites[ i ].image === image ) {
          usedSprite = this.usedSprites[ i ];
          break;
        }
      }
      assert && assert( usedSprite, 'Sprite not found for removeImage' );

      // if we have no more references to the image/sprite
      if ( --usedSprite.count <= 0 ) {
        this.usedSprites.splice( i, 1 ); // remove it from the used list
        this.unusedSprites.push( usedSprite ); // add it to the unused list
      }

      // NOTE: no modification to the Canvas/texture is made, since we can leave it drawn there and unreferenced.
      // If addImage( image ) is called for the same image, we can 'resurrect' it without any further Canvas/texture
      // changes being made.
    },

    /**
     * Whether the sprite for the specified image is handled by this spritesheet. It can be either used or unused, but
     * addImage() calls with the specified image should be extremely fast (no need to modify the Canvas or texture).
     *
     * @returns {boolean}
     */
    containsImage: function( image ) {
      var i;

      // check used cache
      for ( i = 0; i < this.usedSprites.length; i++ ) {
        if ( this.usedSprites[ i ].image === image ) {
          return true;
        }
      }

      // check unused cache
      for ( i = 0; i < this.unusedSprites.length; i++ ) {
        if ( this.unusedSprites[ i ].image === image ) {
          return true;
        }
      }

      return false;
    }
  } );

  /**
   * A reference to a specific part of the texture that can be used.
   *
   * @constructor
   */
  SpriteSheet.Sprite = function( spriteSheet, bin, uvBounds, image, initialCount ) {
    // @public [read-only] {SpriteSheet} - The containing SpriteSheet
    this.spriteSheet = spriteSheet;

    // @private [read-only] {BinPacker.Bin} - Contains the actual image bounds in our Canvas, and is used to deallocate.
    this.bin = bin;

    // @public [read-only] {Bounds2} - Normalized bounds between [0,1] for the full texture (for GLSL texture lookups).
    this.uvBounds = uvBounds;

    // @private [read-only] {HTMLCanvasElement | HTMLImageElement} - Image element used.
    this.image = image;

    // @private [read-write] {number} - Reference count for number of addChild() calls minus removeChild() calls. If
    // the count is 0, it should be in the 'unusedSprites' array, otherwise it should be in the 'usedSprites' array.
    this.count = initialCount;
  };

  // @public (read-only) the size of a sprite sheet
  SpriteSheet.MAX_DIMENSION = MAX_DIMENSION;

  return SpriteSheet;
} );
<commit_msg>Adding padding to the spritesheet, see https://github.com/phetsims/scenery/issues/637<commit_after>// Copyright 2015-2016, University of Colorado Boulder

/**
 * A single Canvas/texture with multiple different images (sprites) drawn internally. During rendering, this texture
 * can be used in one draw call to render multiple different images by providing UV coordinates to each quad for each
 * image to be drawn.
 *
 * Note that the WebGL texture part is not required to be run - the Canvas-only part can be used functionally without
 * any WebGL dependencies.
 *
 * TODO: Add padding around sprites, otherwise interpolation could cause issues!
 * TODO: How to use custom mipmap levels?
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var scenery = require( 'SCENERY/scenery' );
  var inherit = require( 'PHET_CORE/inherit' );
  var BinPacker = require( 'DOT/BinPacker' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Dimension2 = require( 'DOT/Dimension2' );

  // constants
  // The max SpriteSheet size was selected to minimize memory overhead while still accommodating many large images
  // See https://github.com/phetsims/scenery/issues/539
  var MAX_DIMENSION = new Dimension2( 1024, 1024 );

  // Added to the bottom and right of every texture
  var PADDING = 1;

  /**
   * @constructor
   *
   * @param {boolean} useMipmaps - Whether built-in WebGL mipmapping should be used. Higher quality, but may be slower
   *                               to add images (since mipmaps need to be updated).
   */
  function SpriteSheet( useMipmaps ) {
    this.useMipmaps = useMipmaps;

    this.gl = null; // assume later creation of context for now

    // Use the max supported texture size (according to http://codeflow.org/entries/2013/feb/22/how-to-write-portable-webgl/ )
    // TODO: potentially support larger texture sizes based on reported capabilities (could cause fewer draw calls?)
    this.bounds = new Bounds2( 0, 0, MAX_DIMENSION.width, MAX_DIMENSION.height );
    assert && assert( this.bounds.minX === 0 && this.bounds.minY === 0, 'Assumed constraint later on for transforms' );
    this.width = this.bounds.width;
    this.height = this.bounds.height;

    this.canvas = document.createElement( 'canvas' );
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    this.context = this.canvas.getContext( '2d' );

    this.binPacker = new BinPacker( this.bounds );

    this.dirty = true; // @public [read-only] - Used to check if we need to updateTexture()

    this.usedSprites = [];
    this.unusedSprites = []; // works as a LRU cache for removing items when we need to allocate new space
  }

  scenery.register( 'SpriteSheet', SpriteSheet );

  inherit( Object, SpriteSheet, {
    /**
     * Initialize (or reinitialize) ourself with a new GL context. Should be called at least once before updateTexture()
     * @public
     *
     * @param {WebGLRenderingContext} gl
     */
    initializeContext: function( gl ) {
      this.gl = gl;

      this.createTexture();
    },

    /**
     * Allocates and creates a GL texture, configures it, and initializes it with our current Canvas.
     * @private
     */
    createTexture: function() {
      var gl = this.gl;

      this.texture = gl.createTexture();
      gl.bindTexture( gl.TEXTURE_2D, this.texture );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.useMipmaps ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR );
      gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
      gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
      gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas );
      if ( this.useMipmaps ) {
        gl.hint( gl.GENERATE_MIPMAP_HINT, gl.NICEST );
        gl.generateMipmap( gl.TEXTURE_2D );
      }
      gl.bindTexture( gl.TEXTURE_2D, null );

      this.dirty = false;
    },

    /**
     * Updates a pre-existing texture with our current Canvas.
     * @public
     */
    updateTexture: function() {
      assert && assert( this.gl, 'SpriteSheet needs context to updateTexture()' );

      if ( this.dirty ) {
        this.dirty = false;

        var gl = this.gl;

        gl.bindTexture( gl.TEXTURE_2D, this.texture );
        gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.canvas );
        if ( this.useMipmaps ) {
          gl.generateMipmap( gl.TEXTURE_2D );
        }
        gl.bindTexture( gl.TEXTURE_2D, null );
      }
    },

    /**
     * Adds an image (if possible) to our sprite sheet. If successful, will return a {Sprite}, otherwise null.
     * @public
     *
     * @param {HTMLCanvasElement|HTMLImageElement} image
     * @param {number} width - Passed in, since it may not be fully loaded yet?
     * @param {number} height - Passed in, since it may not be fully loaded yet?
     *
     * @returns {Sprite|null}
     */
    addImage: function( image, width, height ) {
      var i;

      // check used cache
      for ( i = 0; i < this.usedSprites.length; i++ ) {
        var usedSprite = this.usedSprites[ i ];
        if ( usedSprite.image === image ) {
          usedSprite.count++;
          return usedSprite;
        }
      }

      // check unused cache
      for ( i = 0; i < this.unusedSprites.length; i++ ) {
        var unusedSprite = this.unusedSprites[ i ];
        if ( unusedSprite.image === image ) {
          unusedSprite.count++;
          assert && assert( unusedSprite.count === 1, 'Count should be exactly 1 after coming back from being unused' );
          this.unusedSprites.splice( i, 1 ); // remove it from the unused array
          this.usedSprites.push( unusedSprite ); // add it to the used array
          return unusedSprite;
        }
      }

      // Not in any caches, let's try to find space. If we can't find space at first, we start removing unused sprites
      // one-by-one.
      var bin;
      // Enters 'while' loop only if allocate() returns null and we have unused sprites (i.e. conditions where we will
      // want to deallocate the least recently used (LRU) unused sprite and then check for allocation again).
      while ( !( bin = this.binPacker.allocate( width + PADDING, height + PADDING ) ) && this.unusedSprites.length ) {
        var ejectedSprite = this.unusedSprites.shift(); // LRU policy by taking first item

        // clear its space in the Canvas
        this.dirty = true;
        var ejectedBounds = ejectedSprite.bin.bounds;
        this.context.clearRect( ejectedBounds.x, ejectedBounds.y, ejectedBounds.width, ejectedBounds.height );

        // deallocate its area in the bin packer
        this.binPacker.deallocate( ejectedSprite.bin );
      }

      if ( bin ) {
        // WebGL will want UV coordinates in the [0,1] range
        var uvBounds = new Bounds2( bin.bounds.minX / this.width, bin.bounds.minY / this.height,
          ( bin.bounds.maxX - PADDING ) / this.width, ( bin.bounds.maxY - PADDING ) / this.height );
        var sprite = new SpriteSheet.Sprite( this, bin, uvBounds, image, 1 );
        this.context.drawImage( image, bin.bounds.x, bin.bounds.y );
        this.dirty = true;
        this.usedSprites.push( sprite );
        return sprite;
      }
      // no space, even after clearing out our unused sprites
      else {
        return null;
      }
    },

    /**
     * Removes an image from our spritesheet. (Removes one from the amount it is used, and if it is 0, gets actually
     * removed).
     * @public
     *
     * @param {HTMLCanvasElement|HTMLImageElement} image
     */
    removeImage: function( image ) {
      // find the used sprite (and its index)
      var usedSprite;
      var i;
      for ( i = 0; i < this.usedSprites.length; i++ ) {
        if ( this.usedSprites[ i ].image === image ) {
          usedSprite = this.usedSprites[ i ];
          break;
        }
      }
      assert && assert( usedSprite, 'Sprite not found for removeImage' );

      // if we have no more references to the image/sprite
      if ( --usedSprite.count <= 0 ) {
        this.usedSprites.splice( i, 1 ); // remove it from the used list
        this.unusedSprites.push( usedSprite ); // add it to the unused list
      }

      // NOTE: no modification to the Canvas/texture is made, since we can leave it drawn there and unreferenced.
      // If addImage( image ) is called for the same image, we can 'resurrect' it without any further Canvas/texture
      // changes being made.
    },

    /**
     * Whether the sprite for the specified image is handled by this spritesheet. It can be either used or unused, but
     * addImage() calls with the specified image should be extremely fast (no need to modify the Canvas or texture).
     * @public
     *
     * @returns {boolean}
     */
    containsImage: function( image ) {
      var i;

      // check used cache
      for ( i = 0; i < this.usedSprites.length; i++ ) {
        if ( this.usedSprites[ i ].image === image ) {
          return true;
        }
      }

      // check unused cache
      for ( i = 0; i < this.unusedSprites.length; i++ ) {
        if ( this.unusedSprites[ i ].image === image ) {
          return true;
        }
      }

      return false;
    }
  } );

  /**
   * A reference to a specific part of the texture that can be used.
   *
   * @constructor
   */
  SpriteSheet.Sprite = function( spriteSheet, bin, uvBounds, image, initialCount ) {
    // @public [read-only] {SpriteSheet} - The containing SpriteSheet
    this.spriteSheet = spriteSheet;

    // @private [read-only] {BinPacker.Bin} - Contains the actual image bounds in our Canvas (plus padding), and is
    //                                        used to deallocate (need to clear that area).
    this.bin = bin;

    // @public [read-only] {Bounds2} - Normalized bounds between [0,1] for the full texture (for GLSL texture lookups).
    this.uvBounds = uvBounds;

    // @private [read-only] {HTMLCanvasElement | HTMLImageElement} - Image element used.
    this.image = image;

    // @private [read-write] {number} - Reference count for number of addChild() calls minus removeChild() calls. If
    // the count is 0, it should be in the 'unusedSprites' array, otherwise it should be in the 'usedSprites' array.
    this.count = initialCount;
  };

  // @public (read-only) the size of a sprite sheet
  SpriteSheet.MAX_DIMENSION = MAX_DIMENSION;

  return SpriteSheet;
} );
<|endoftext|>


<commit_before>define([
	'jquery',
	'underscore',
	'backbone',
	'text!templates/style/menu.html',
	'jscssp',
	'config',
	'libs/marked/marked'
],
function($, _, Backbone, dashboardPageTemplate, jscssp, config, marked) {
	var DashboardPage = Backbone.View.extend({
		el: '.js-phytoplankton-menu',
		events: {
			'click .phytoplankton-menu__list__item__link': function (ev) {
				$('.js-phytoplankton-menu').removeClass('is-active');
				$('body').removeClass('is-opaque is-locked');
				if($(ev.currentTarget).hasClass('active')) {
					ev.preventDefault();
				} else {
					this.$el.find('.active').removeClass('active');
					$(ev.currentTarget).addClass('active');
					$(ev.currentTarget).parent().find('li:first-child').addClass('active');
				}
			},
			'click .phytoplankton-menu__list__item ul li ul li a': function(ev) {
				ev.preventDefault();
				var scrollAnchor = $(ev.currentTarget).attr('href');
				var scrollAnchor = scrollAnchor.substr(scrollAnchor.lastIndexOf('#') + 1);
				var scrollPoint = $('.phytoplankton-page__item *[id="' + scrollAnchor + '"]').offset().top - (50 + 40);
				$('html, body').animate({
					scrollTop: scrollPoint
				}, '200');
			}
		},
		render: function () {

			var that = this;

			that.$el.html('Loading styles');

			var page = {blocks:[]};

			require(['text!' + config.css_path], function (styles) {
				// Default "imports.css"
				var markedOpts = _.extend({
										sanitize: false,
										gfm: true
									},
									config.marked_options || {}
								);
				marked.setOptions(markedOpts);

				var parser = new jscssp();
				var stylesheet = parser.parse(styles, false, true);
				var menus = [];
				var menuTitle = '';
				var currentMenu = {
					sheets: [],
					category: ''
				};
				var sheetPath;

				_.each(stylesheet.cssRules, function(rule) {
					// If /* Comment */
					if(rule.type === 101) {
						var comment = rule.parsedCssText;
						comment = comment.replace('/*', '');
						comment = comment.replace('*/', '');

						var comments = marked.lexer(comment);
						var defLinks = comments.links || {};
						_.each(comments, function (comment) {
							var tokens = [comment];
							tokens.links = defLinks;
							if(comment.type === 'heading' && comment.depth === 1) {
								menus.push(_.extend({}, currentMenu));
								currentMenu.sheets = [];
								currentMenu.category = comment.text;
							}
						});
					}
					// If @import url('');
					if(rule.type === 3) {
						// Removes @import url(''); leaving just the style sheet name.
						var sheet = rule.href.substr(rule.href.indexOf('(')+2, rule.href.indexOf(')')-rule.href.indexOf('(')-3);

						var regex = /(?:.*\/)(.*)\.(css|sass|scss|less)$/gi;
						var result = [];
						if((result = regex.exec(sheet)) !== null) {
							// result[0] Original Input.
							// result[1] Filename.
							// result[2] Extension.

							// Returns the path before 'extension/'.
							sheetPath = result[0].substr(0, result[0].lastIndexOf('' + result[2] + '/') + (result[2].length + 1));
							// Removes 'sheetPath' from 'sheet' leaving the path after 'extension/'.
							sheet = result[0].replace(sheetPath, '');
						}
						// Pushes style sheet to currentMenu.
						currentMenu.sheets.push(sheet);
					}
				});

				if(config.css_paths) {
					for(var i = 0; i < config.css_paths.length; i++) {
						var regex = /(?:.*\/)(.*)\.(css|sass|scss|less)$/gi;
						var result = [];
						if((result = regex.exec(config.css_paths[i])) !== null) {
							// result[0] Original Input.
							// result[1] Filename.
							// result[2] Extension.

							// Returns the path before 'extension/'.
							sheetPath = result[0].substr(0, result[0].lastIndexOf('' + result[2] + '/') + (result[2].length + 1));
							// Removes 'sheetPath' from 'sheet' leaving the path after 'extension/'.
							config.css_paths[i] = result[0].replace(sheetPath, '');
						}
						// Pushes style sheet to currentMenu.
						currentMenu.sheets.push(config.css_paths[i]);
					}
				}

				menus.push(currentMenu);

				$(that.el).html(_.template(dashboardPageTemplate, {_:_, menus: menus}));
				$('[href="' + window.location.hash + '"]').addClass('active');
				if(window.location.hash === '') {
					$('.js-phytoplankton-home').addClass('active');
				}

			});
		}
	});
	return DashboardPage;
});<commit_msg>Fixed left menu click function<commit_after>define([
	'jquery',
	'underscore',
	'backbone',
	'text!templates/style/menu.html',
	'jscssp',
	'config',
	'libs/marked/marked'
],
function($, _, Backbone, dashboardPageTemplate, jscssp, config, marked) {
	var DashboardPage = Backbone.View.extend({
		el: '.js-phytoplankton-menu',
		events: {
			'click .phytoplankton-menu__list__item__link': function (ev) {
				if($(ev.currentTarget).hasClass('active')) {
					ev.preventDefault();
				} else {
					this.$el.find('.active').removeClass('active');
					$(ev.currentTarget).addClass('active');
					$(ev.currentTarget).parent().find('li:first-child').addClass('active');
				}
			},
			'click .phytoplankton-menu__list__item a': function() {
				$('.js-phytoplankton-menu').removeClass('is-active');
				$('body').removeClass('is-opaque is-locked');
			},
			'click .phytoplankton-menu__list__item ul li ul li a': function(ev) {
				ev.preventDefault();
				var scrollAnchor = $(ev.currentTarget).attr('href');
				var scrollAnchor = scrollAnchor.substr(scrollAnchor.lastIndexOf('#') + 1);
				var scrollPoint = $('.phytoplankton-page__item *[id="' + scrollAnchor + '"]').offset().top - (50 + 40);
				$('html, body').animate({
					scrollTop: scrollPoint
				}, '200');
			}
		},
		render: function () {

			var that = this;

			that.$el.html('Loading styles');

			var page = {blocks:[]};

			require(['text!' + config.css_path], function (styles) {
				// Default "imports.css"
				var markedOpts = _.extend({
										sanitize: false,
										gfm: true
									},
									config.marked_options || {}
								);
				marked.setOptions(markedOpts);

				var parser = new jscssp();
				var stylesheet = parser.parse(styles, false, true);
				var menus = [];
				var menuTitle = '';
				var currentMenu = {
					sheets: [],
					category: ''
				};
				var sheetPath;

				_.each(stylesheet.cssRules, function(rule) {
					// If /* Comment */
					if(rule.type === 101) {
						var comment = rule.parsedCssText;
						comment = comment.replace('/*', '');
						comment = comment.replace('*/', '');

						var comments = marked.lexer(comment);
						var defLinks = comments.links || {};
						_.each(comments, function (comment) {
							var tokens = [comment];
							tokens.links = defLinks;
							if(comment.type === 'heading' && comment.depth === 1) {
								menus.push(_.extend({}, currentMenu));
								currentMenu.sheets = [];
								currentMenu.category = comment.text;
							}
						});
					}
					// If @import url('');
					if(rule.type === 3) {
						// Removes @import url(''); leaving just the style sheet name.
						var sheet = rule.href.substr(rule.href.indexOf('(')+2, rule.href.indexOf(')')-rule.href.indexOf('(')-3);

						var regex = /(?:.*\/)(.*)\.(css|sass|scss|less)$/gi;
						var result = [];
						if((result = regex.exec(sheet)) !== null) {
							// result[0] Original Input.
							// result[1] Filename.
							// result[2] Extension.

							// Returns the path before 'extension/'.
							sheetPath = result[0].substr(0, result[0].lastIndexOf('' + result[2] + '/') + (result[2].length + 1));
							// Removes 'sheetPath' from 'sheet' leaving the path after 'extension/'.
							sheet = result[0].replace(sheetPath, '');
						}
						// Pushes style sheet to currentMenu.
						currentMenu.sheets.push(sheet);
					}
				});

				if(config.css_paths) {
					for(var i = 0; i < config.css_paths.length; i++) {
						var regex = /(?:.*\/)(.*)\.(css|sass|scss|less)$/gi;
						var result = [];
						if((result = regex.exec(config.css_paths[i])) !== null) {
							// result[0] Original Input.
							// result[1] Filename.
							// result[2] Extension.

							// Returns the path before 'extension/'.
							sheetPath = result[0].substr(0, result[0].lastIndexOf('' + result[2] + '/') + (result[2].length + 1));
							// Removes 'sheetPath' from 'sheet' leaving the path after 'extension/'.
							config.css_paths[i] = result[0].replace(sheetPath, '');
						}
						// Pushes style sheet to currentMenu.
						currentMenu.sheets.push(config.css_paths[i]);
					}
				}

				menus.push(currentMenu);

				$(that.el).html(_.template(dashboardPageTemplate, {_:_, menus: menus}));
				$('[href="' + window.location.hash + '"]').addClass('active');
				if(window.location.hash === '') {
					$('.js-phytoplankton-home').addClass('active');
				}

			});
		}
	});
	return DashboardPage;
});<|endoftext|>


<commit_before>define([
  'jquery',
  'underscore',
  'backbone',
  'text!templates/style/page.html',
  'less',
  'config',
  'pagedown',
  'libs/marked/marked',
  'libs/highlight/highlight',
  'libs/parseuri/parseuri',
  'libs/waypoints/waypoints'
], function($, _, Backbone, stylePageTemplate, less, config, Pagedown, marked, hljs, parseuri, waypoints){
  var StylePage = Backbone.View.extend({
    el: '.kalei-style-page',
    render: function () {
      var that = this,
      	  $sheet = $('<link rel="stylesheet/less" href="' + config.css_path + '" />');

      // update menu to indicate what's being viewed
        $('a.kalei-styleguide-menu-link').removeClass('active');
        $('[href="' + window.location.hash + '"]').addClass('active');
        if(window.location.hash === '') {
          $('.js-kalei-home').addClass('active');
        }

      // determine the URL to the style we're currently viewing
      var styleUrl;
      if(this.options.style === null) {
        this.options.style = config.css_path.substr(config.css_path.lastIndexOf('/')+1);
      }
      if(this.options.style.substr(0,1) === '/') {
        // non relative
        var configDir = config.css_path.substr(0, config.css_path.lastIndexOf('/'));
        var pUrl = parseuri(configDir);
        styleUrl = pUrl.protocol + '://' + pUrl.host + (pUrl.port === '' ? '' : ':'+ pUrl) + this.options.style;
      } else {
        var configDir = config.css_path.substr(0, config.css_path.lastIndexOf('/'));
        styleUrl = configDir + '/' + this.options.style;
      }

      // init the LESS parser
      var parser = new(less.Parser)({
			paths: [configDir + '/', './'], 		// Specify search paths for @import directives
			filename: config.css_path.substr(config.css_path.lastIndexOf('/')+1)
		});

      // render all the CSS we need to deal with
      $('head').append($sheet);
      less.sheets.push($sheet[0]);
      less.refresh(true);

      // parse it for demo markup and render to the page
	   require(['text!'+ styleUrl], function (stylesheet){

        marked.setOptions({ sanitize: false, gfm: true });

		var blocks = [],
	        currentBlock = {
	          comments: [],
	          css: ''
	        },
        	headings = [];

	 	parser.parse(stylesheet, function(err, tree) {
			if (err) { return console.error(err); }

			// parse comments with MarkDown
			_.each(tree.rules, function(rule) {
				if (rule.value !== undefined && rule.rules === undefined && !rule.variable) {
					var comment = rule.value;

					// skip single-line comments
					if (comment.substr(0, 2) == '//') {
						return;
					}

		            comment = comment.replace('/*', '');
		            comment = comment.replace('*/', '');

		            var comments = marked.lexer(comment);
		            _.each(comments, function (comment) {

		              if(comment.type === 'heading' && comment.depth <= 2) {
		                headings.push(comment.text);
		                currentBlock.css = css_beautify(currentBlock.css);
		                if(currentBlock.comments.length !== 0 || currentBlock.css !== '') {
		                  currentBlock.comments = marked.parser(currentBlock.comments);
		                  blocks.push(_.extend({}, currentBlock));
		                  currentBlock.comments = [];
		                  currentBlock.css = '';
		                }
		              }
		              if(comment.type === 'code'){
		                currentBlock.comments.push({
		                  type: 'html',
		                  text: '
' + comment.text + '

'
		                })
		              };
		              currentBlock.comments.push(comment);

		            });
				}
			});

			currentBlock.css += tree.toCSS();	// append all CSS to the input
		});

		// draw menu
      	$('.sheet-submenu').slideUp(200);

        var submenu = $('
');
        _.each(headings, function (heading) {
          submenu.append($('
').text(heading));
        });
        $('li:first-child', submenu).addClass('active');
        $('.sheet-submenu', $('[data-sheet="' + that.options.style + '"]')).html(submenu).slideDown(200);

        currentBlock.css = css_beautify(currentBlock.css);
        currentBlock.comments = marked.parser(currentBlock.comments);
        blocks.push(currentBlock);

        // draw page
        $(that.el).html(_.template(stylePageTemplate, {_:_, blocks: blocks, config: config}));

        // syntax highlighting
        $(' code').each(function(i, e) {hljs.highlightBlock(e); });

        $('.kalei-comments-container > .kalei-comments > h2, .kalei-comments-container > .kalei-comments  > h1').waypoint(function(ev) {
          console.log(arguments);
         $('.sheet-submenu li').removeClass('active');
           $('.sheet-submenu li:contains('+$(ev.currentTarget).text()+')').addClass('active');
        }, {
   offset: 20  // middle of the page
});

        $("body").on('click', '.sheet-submenu li', function(ev) {

             $('html, body').animate({
                 scrollTop: $(".kalei-comments h2:contains('"+$(ev.currentTarget).text()+"'),.kalei-comments h1:contains('"+$(ev.currentTarget).text()+"')").offset().top - 20
             }, 200);
         });

         fixie.init();

      });

    }
  });
  return StylePage;
});
<commit_msg>do not render CSS files as less to avoid incorrect parsing of @import directives for the rendered sheet<commit_after>define([
  'jquery',
  'underscore',
  'backbone',
  'text!templates/style/page.html',
  'less',
  'config',
  'pagedown',
  'libs/marked/marked',
  'libs/highlight/highlight',
  'libs/parseuri/parseuri',
  'libs/waypoints/waypoints'
], function($, _, Backbone, stylePageTemplate, less, config, Pagedown, marked, hljs, parseuri, waypoints){
  var StylePage = Backbone.View.extend({
    el: '.kalei-style-page',
    render: function () {
      var that = this, $sheet;

      // update menu to indicate what's being viewed
        $('a.kalei-styleguide-menu-link').removeClass('active');
        $('[href="' + window.location.hash + '"]').addClass('active');
        if(window.location.hash === '') {
          $('.js-kalei-home').addClass('active');
        }

      // determine the URL to the style we're currently viewing
      var styleUrl, isLess;
      if(this.options.style === null) {
        this.options.style = config.css_path.substr(config.css_path.lastIndexOf('/')+1);
      }
      if(this.options.style.substr(0,1) === '/') {
        // non relative
        var configDir = config.css_path.substr(0, config.css_path.lastIndexOf('/'));
        var pUrl = parseuri(configDir);
        styleUrl = pUrl.protocol + '://' + pUrl.host + (pUrl.port === '' ? '' : ':'+ pUrl) + this.options.style;
      } else {
        var configDir = config.css_path.substr(0, config.css_path.lastIndexOf('/'));
        styleUrl = configDir + '/' + this.options.style;
      }

      isLess = this.options.style.match(/\.less$/i) !== null;

      // init the LESS parser
      var parser = new(less.Parser)({
			paths: [configDir + '/', './'], 		// Specify search paths for @import directives
			filename: config.css_path.substr(config.css_path.lastIndexOf('/')+1)
		});

      // render all the CSS we need to deal with

      if (isLess) {
      	$sheet = $('<link rel="stylesheet/less" href="' + config.css_path + '" />')
		$('head').append($sheet);
		less.sheets.push($sheet[0]);
		less.refresh(true);
      } else {
      	$sheet = $('<link rel="stylesheet" href="' + config.css_path + '" />')
		$('head').append($sheet);
      }

      // parse it for demo markup and render to the page
	   require(['text!'+ styleUrl], function (stylesheet){

        marked.setOptions({ sanitize: false, gfm: true });

		var blocks = [],
	        currentBlock = {
	          comments: [],
	          css: ''
	        },
        	headings = [];

	 	parser.parse(stylesheet, function(err, tree) {
			if (err) { return console.error(err); }

			// parse comments with MarkDown
			_.each(tree.rules, function(rule) {
				if (rule.value !== undefined && rule.rules === undefined && !rule.variable) {
					var comment = rule.value;

					// skip single-line comments
					if (comment.substr(0, 2) == '//') {
						return;
					}

		            comment = comment.replace('/*', '');
		            comment = comment.replace('*/', '');

		            var comments = marked.lexer(comment);
		            _.each(comments, function (comment) {

		              if(comment.type === 'heading' && comment.depth <= 2) {
		                headings.push(comment.text);
		                currentBlock.css = css_beautify(currentBlock.css);
		                if(currentBlock.comments.length !== 0 || currentBlock.css !== '') {
		                  currentBlock.comments = marked.parser(currentBlock.comments);
		                  blocks.push(_.extend({}, currentBlock));
		                  currentBlock.comments = [];
		                  currentBlock.css = '';
		                }
		              }
		              if(comment.type === 'code'){
		                currentBlock.comments.push({
		                  type: 'html',
		                  text: '
' + comment.text + '

'
		                })
		              };
		              currentBlock.comments.push(comment);

		            });
				}
			});

			currentBlock.css += tree.toCSS();	// append all CSS to the input
		});

		// draw menu
      	$('.sheet-submenu').slideUp(200);

        var submenu = $('
');
        _.each(headings, function (heading) {
          submenu.append($('
').text(heading));
        });
        $('li:first-child', submenu).addClass('active');
        $('.sheet-submenu', $('[data-sheet="' + that.options.style + '"]')).html(submenu).slideDown(200);

        currentBlock.css = css_beautify(currentBlock.css);
        currentBlock.comments = marked.parser(currentBlock.comments);
        blocks.push(currentBlock);

        // draw page
        $(that.el).html(_.template(stylePageTemplate, {_:_, blocks: blocks, config: config}));

        // syntax highlighting
        $(' code').each(function(i, e) {hljs.highlightBlock(e); });

        $('.kalei-comments-container > .kalei-comments > h2, .kalei-comments-container > .kalei-comments  > h1').waypoint(function(ev) {
          console.log(arguments);
         $('.sheet-submenu li').removeClass('active');
           $('.sheet-submenu li:contains('+$(ev.currentTarget).text()+')').addClass('active');
        }, {
   offset: 20  // middle of the page
});

        $("body").on('click', '.sheet-submenu li', function(ev) {

             $('html, body').animate({
                 scrollTop: $(".kalei-comments h2:contains('"+$(ev.currentTarget).text()+"'),.kalei-comments h1:contains('"+$(ev.currentTarget).text()+"')").offset().top - 20
             }, 200);
         });

         fixie.init();

      });

    }
  });
  return StylePage;
});
<|endoftext|>


<commit_before>

// A history of rolls sent to the Table
Characters = new Meteor.Collection("characters");

function pad(number, length) {
    var str = '' + number;
    while (str.length < length) {
        str = '0' + str;1
    }
    return str;
}

function formattime(hour, minutes) {
  if (hour >= 12) {
    ampm = 'pm'
  } else {
    ampm = 'am'
  }

  hour = hour % 12;
  if (hour == 0) {
    hour = 12;
  }

  return hour+":"+pad(minutes,2)+ampm;
}

if (Meteor.isClient) {

  var highestMaxLabor = function () {
    return Characters.findOne({owner: Session.get('sessionid')}, {sort: {labormax: -1}}).labormax;
  };

  Session.set('sessionid', location.search);

  // if (!Session.get('sessionid')) {
    // sessionid = Math.floor(Math.random() * 100000000000000)
  // }

  // When editing a character name, ID of the character
  Session.set('editing_charactername', null);

  // When editing current labor, ID of the character
  Session.set('editing_characterlabor', null);

  // When editing current labormax, ID of the character
  Session.set('editing_characterlabormax', null);

/* New version
  Deps.autorun(function () {
    Meteor.subscribe("characters");
  });
*/

  Meteor.autosubscribe(function () {
      Meteor.subscribe('characters', {owner: Session.get('sessionid')});
  });

  ////////// Helpers for in-place editing //////////

  // Returns an event map that handles the "escape" and "return" keys and
  // "blur" events on a text input (given by selector) and interprets them
  // as "ok" or "cancel".
  var okCancelEvents = function (selector, callbacks) {
    var ok = callbacks.ok || function () {};
    var cancel = callbacks.cancel || function () {};

    var events = {};
    events['keyup '+selector+', keydown '+selector+', focusout '+selector] =
      function (evt) {
        if (evt.type === "keydown" && evt.which === 27) {
          // escape = cancel
          cancel.call(this, evt);

        } else if (evt.type === "keyup" && evt.which === 13 ||
                   evt.type === "focusout") {
          // blur/return/enter = ok/submit if non-empty
          var value = String(evt.target.value || "");
          if (value)
            ok.call(this, value, evt);
          else
            cancel.call(this, evt);
        }
      };
    return events;
  };

  var activateInput = function (input) {
    input.focus();
    input.select();
  };

  /////////// NEED SESSION PAGE ///////////
  Template.needsession.events({
    'click input.sessionnamesubmit' : function () {
      window.location = Meteor.absoluteUrl('?' + $("#sessionname").val())
    }
  });

  ////////////// MAIN TEMPLATE //////////////
  Template.main.characters = function () {
    return Characters.find({owner: Session.get('sessionid')}, {});
  };

  Template.main.events({
    'click input.addcharacter' : function () {
      Characters.insert({name: 'NewCharacter', labor: 50, labormax: 1000, labortimestamp: Date.now(), owner: Session.get('sessionid')});
    }
  });

  Template.main.need_session = function () {
    return Session.get('sessionid') == "" || Session.get('sessionid') == "?undefined" || Session.get('sessionid') == "?";
  };

  ////////////// CHARACTERS ///////////////
  var timerDep = new Deps.Dependency;
  var timerUpdate = function () {
    timerDep.changed();
  };
  Meteor.setInterval(timerUpdate, 60000);

  Template.character.currentlabor = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60) + this.labor;
    return currentlabor;
  };

  Template.character.currentlaborcapped = function() {
    timerDep.depend();
    return Math.min(this.labormax,Math.floor((Date.now() - this.labortimestamp) / 1000 / 60) + this.labor);
  };

  // Returns the percentage of max labor, in integer format (50 for 50%)
  Template.character.percentage = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60) + this.labor;
    return Math.min(100,Math.floor(currentlabor / this.labormax * 100))
  };

  // Returns the percentage of this character's max labor compared to,
  // the character with the MOST max labor. Integer format (50 for 50%)
  Template.character.percentagemax = function() {
    timerDep.depend();
    return Math.min(100,Math.floor(this.labormax / highestMaxLabor() * 100))
  };

  Template.character.laborcapped = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60) + this.labor;

    return (currentlabor >= this.labormax);
  }

  Template.character.laborwaste = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60) + this.labor;

    return Math.max(0,currentlabor - this.labormax);
  }

  Template.character.maxtime = function() {
    var maxtimestamp = this.labortimestamp + (this.labormax - this.labor) * 1000 * 60;
    var date = new Date(maxtimestamp);
    var hour = date.getHours();
    var minutes = date.getMinutes();

    var hoursleft = Math.floor(Math.abs((maxtimestamp - Date.now()) / 1000 / 60 / 60))
    var minutesleft = Math.floor(Math.abs((maxtimestamp - Date.now()) / 1000 / 60 % 60))

    return formattime(hour,minutes)+" ("+hoursleft+"h "+minutesleft+"m)";
  };

  Template.character.events({
    'click a.remove' : function () {
      Characters.remove({_id: this._id});
    },
    'click div.name': function (evt, tmpl) { // start editing list name
      Session.set('editing_charactername', this._id);
      Meteor.flush(); // force DOM redraw, so we can focus the edit field
      activateInput(tmpl.find("#character-name-input"));
    },
    'click div.labor': function (evt, tmpl) { // start editing list name
      Session.set('editing_characterlabor', this._id);
      Meteor.flush(); // force DOM redraw, so we can focus the edit field
      activateInput(tmpl.find("#character-labor-input"));
    },
    'click div.labormax': function (evt, tmpl) { // start editing list name
      Session.set('editing_characterlabormax', this._id);
      Meteor.flush(); // force DOM redraw, so we can focus the edit field
      activateInput(tmpl.find("#character-labormax-input"));
    }
  });

  Template.character.events(okCancelEvents(
    '#character-name-input', {
      ok: function (value) {
        Characters.update(this._id, {$set: {name: value}});
        Session.set('editing_charactername', null);
      },
      cancel: function () {
        Session.set('editing_charactername', null);
      }
    }
  ));

  Template.character.events(okCancelEvents(
    '#character-labor-input', {
      ok: function (value) {
        Characters.update(this._id, {$set: {labor: Number(value), labortimestamp: Date.now()}});
        Session.set('editing_characterlabor', null);
      },
      cancel: function () {
        Session.set('editing_characterlabor', null);
      }
    }
  ));

  Template.character.events(okCancelEvents(
    '#character-labormax-input', {
      ok: function (value) {
        Characters.update(this._id, {$set: {labormax: Number(value)}});
        Session.set('editing_characterlabormax', null);
      },
      cancel: function () {
        Session.set('editing_characterlabormax', null);
      }
    }
  ));

  Template.character.editingname = function () {
    return Session.equals('editing_charactername', this._id);
  };

  Template.character.editinglabor = function () {
    return Session.equals('editing_characterlabor', this._id);
  };

  Template.character.editinglabormax = function () {
    return Session.equals('editing_characterlabormax', this._id);
  };

}

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
  });
}<commit_msg>Updated labor generation rate to 2 per sec<commit_after>

// A history of rolls sent to the Table
Characters = new Meteor.Collection("characters");

// How much labor you generate per minute
var LABORGENRATE = 2

function pad(number, length) {
    var str = '' + number;
    while (str.length < length) {
        str = '0' + str;1
    }
    return str;
}

function formattime(hour, minutes) {
  if (hour >= 12) {
    ampm = 'pm'
  } else {
    ampm = 'am'
  }

  hour = hour % 12;
  if (hour == 0) {
    hour = 12;
  }

  return hour+":"+pad(minutes,2)+ampm;
}

if (Meteor.isClient) {

  var highestMaxLabor = function () {
    return Characters.findOne({owner: Session.get('sessionid')}, {sort: {labormax: -1}}).labormax;
  };

  Session.set('sessionid', location.search);

  // if (!Session.get('sessionid')) {
    // sessionid = Math.floor(Math.random() * 100000000000000)
  // }

  // When editing a character name, ID of the character
  Session.set('editing_charactername', null);

  // When editing current labor, ID of the character
  Session.set('editing_characterlabor', null);

  // When editing current labormax, ID of the character
  Session.set('editing_characterlabormax', null);

/* New version
  Deps.autorun(function () {
    Meteor.subscribe("characters");
  });
*/

  Meteor.autosubscribe(function () {
      Meteor.subscribe('characters', {owner: Session.get('sessionid')});
  });

  ////////// Helpers for in-place editing //////////

  // Returns an event map that handles the "escape" and "return" keys and
  // "blur" events on a text input (given by selector) and interprets them
  // as "ok" or "cancel".
  var okCancelEvents = function (selector, callbacks) {
    var ok = callbacks.ok || function () {};
    var cancel = callbacks.cancel || function () {};

    var events = {};
    events['keyup '+selector+', keydown '+selector+', focusout '+selector] =
      function (evt) {
        if (evt.type === "keydown" && evt.which === 27) {
          // escape = cancel
          cancel.call(this, evt);

        } else if (evt.type === "keyup" && evt.which === 13 ||
                   evt.type === "focusout") {
          // blur/return/enter = ok/submit if non-empty
          var value = String(evt.target.value || "");
          if (value)
            ok.call(this, value, evt);
          else
            cancel.call(this, evt);
        }
      };
    return events;
  };

  var activateInput = function (input) {
    input.focus();
    input.select();
  };

  /////////// NEED SESSION PAGE ///////////
  Template.needsession.events({
    'click input.sessionnamesubmit' : function () {
      window.location = Meteor.absoluteUrl('?' + $("#sessionname").val())
    }
  });

  ////////////// MAIN TEMPLATE //////////////
  Template.main.characters = function () {
    return Characters.find({owner: Session.get('sessionid')}, {});
  };

  Template.main.events({
    'click input.addcharacter' : function () {
      Characters.insert({name: 'NewCharacter', labor: 50, labormax: 1000, labortimestamp: Date.now(), owner: Session.get('sessionid')});
    }
  });

  Template.main.need_session = function () {
    return Session.get('sessionid') == "" || Session.get('sessionid') == "?undefined" || Session.get('sessionid') == "?";
  };

  ////////////// CHARACTERS ///////////////
  var timerDep = new Deps.Dependency;
  var timerUpdate = function () {
    timerDep.changed();
  };
  Meteor.setInterval(timerUpdate, 60000 / LABORGENRATE);

  Template.character.currentlabor = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60 * LABORGENRATE) + this.labor;
    return currentlabor;
  };

  Template.character.currentlaborcapped = function() {
    timerDep.depend();
    return Math.min(this.labormax,Math.floor((Date.now() - this.labortimestamp) / 1000 / 60 * LABORGENRATE) + this.labor);
  };

  // Returns the percentage of max labor, in integer format (50 for 50%)
  Template.character.percentage = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60 * LABORGENRATE) + this.labor;
    return Math.min(100,Math.floor(currentlabor / this.labormax * 100))
  };

  // Returns the percentage of this character's max labor compared to,
  // the character with the MOST max labor. Integer format (50 for 50%)
  Template.character.percentagemax = function() {
    timerDep.depend();
    return Math.min(100,Math.floor(this.labormax / highestMaxLabor() * 100))
  };

  Template.character.laborcapped = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60 * LABORGENRATE) + this.labor;

    return (currentlabor >= this.labormax);
  }

  Template.character.laborwaste = function() {
    timerDep.depend();
    var currentlabor = Math.floor((Date.now() - this.labortimestamp) / 1000 / 60 * LABORGENRATE) + this.labor;

    return Math.max(0,currentlabor - this.labormax);
  }

  Template.character.maxtime = function() {
    var maxtimestamp = this.labortimestamp + (this.labormax - this.labor) * 1000 * 60 / LABORGENRATE;
    var date = new Date(maxtimestamp);
    var hour = date.getHours();
    var minutes = date.getMinutes();

    var hoursleft = Math.floor(Math.abs((maxtimestamp - Date.now()) / 1000 / 60 / 60))
    var minutesleft = Math.floor(Math.abs((maxtimestamp - Date.now()) / 1000 / 60 % 60))

    return formattime(hour,minutes)+" ("+hoursleft+"h "+minutesleft+"m)";
  };

  Template.character.events({
    'click a.remove' : function () {
      Characters.remove({_id: this._id});
    },
    'click div.name': function (evt, tmpl) { // start editing list name
      Session.set('editing_charactername', this._id);
      Meteor.flush(); // force DOM redraw, so we can focus the edit field
      activateInput(tmpl.find("#character-name-input"));
    },
    'click div.labor': function (evt, tmpl) { // start editing list name
      Session.set('editing_characterlabor', this._id);
      Meteor.flush(); // force DOM redraw, so we can focus the edit field
      activateInput(tmpl.find("#character-labor-input"));
    },
    'click div.labormax': function (evt, tmpl) { // start editing list name
      Session.set('editing_characterlabormax', this._id);
      Meteor.flush(); // force DOM redraw, so we can focus the edit field
      activateInput(tmpl.find("#character-labormax-input"));
    }
  });

  Template.character.events(okCancelEvents(
    '#character-name-input', {
      ok: function (value) {
        Characters.update(this._id, {$set: {name: value}});
        Session.set('editing_charactername', null);
      },
      cancel: function () {
        Session.set('editing_charactername', null);
      }
    }
  ));

  Template.character.events(okCancelEvents(
    '#character-labor-input', {
      ok: function (value) {
        Characters.update(this._id, {$set: {labor: Number(value), labortimestamp: Date.now()}});
        Session.set('editing_characterlabor', null);
      },
      cancel: function () {
        Session.set('editing_characterlabor', null);
      }
    }
  ));

  Template.character.events(okCancelEvents(
    '#character-labormax-input', {
      ok: function (value) {
        Characters.update(this._id, {$set: {labormax: Number(value)}});
        Session.set('editing_characterlabormax', null);
      },
      cancel: function () {
        Session.set('editing_characterlabormax', null);
      }
    }
  ));

  Template.character.editingname = function () {
    return Session.equals('editing_charactername', this._id);
  };

  Template.character.editinglabor = function () {
    return Session.equals('editing_characterlabor', this._id);
  };

  Template.character.editinglabormax = function () {
    return Session.equals('editing_characterlabormax', this._id);
  };

}

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
  });
}<|endoftext|>


<commit_before>/* eslint-env node */

const base = require('./base.conf');

module.exports = function (config) {
	config.set(Object.assign({}, base, {
		plugins: base.plugins.concat(['karma-coverage', 'karma-electron']),
		browsers: ['Electron'],
		reporters: base.reporters.concat(['coverage']),
		coverageReporter: {
			dir: './coverage/',
			reporters: [
				{ type: 'html' },
				{ type: 'json' },
			]
		},
		preprocessors: {
			'ractive.js': ['coverage'],
			'*.js': ['electron'],
		},
		files: [
			'ractive.js',
			'tests-node.js'
		],
		client: Object.assign({}, base.client, {
			useIframe: false
		})
	}));
};
<commit_msg>Only add require and __filename support to test, not lib. Does some stuff to lib that throws off coverage.<commit_after>/* eslint-env node */

const base = require('./base.conf');

module.exports = function (config) {
	config.set(Object.assign({}, base, {
		plugins: base.plugins.concat(['karma-coverage', 'karma-electron']),
		browsers: ['Electron'],
		reporters: base.reporters.concat(['coverage']),
		coverageReporter: {
			dir: './coverage/',
			reporters: [
				{ type: 'html' },
				{ type: 'json' },
			]
		},
		preprocessors: {
			'ractive.js': ['coverage'],
			'tests-node.js': ['electron'],
		},
		files: [
			'ractive.js',
			'tests-node.js'
		],
		client: Object.assign({}, base.client, {
			useIframe: false
		})
	}));
};
<|endoftext|>