<commit_before>let iterationFinished = false;
let ws;

function setWithDataURL(url, elem) {
    let isFirefox = navigator.userAgent.search("Firefox") > -1;
    let img = $("<img>");
    img.attr("class", "replace");
    img[0].onload = () => {
        let h = img[0].naturalHeight;
        let w = img[0].naturalWidth;
        let scale = parseInt($(elem).css("width")) / Math.max(h, w);
        img.attr("height", Math.min(h, h * scale));
        img.attr("width", Math.min(w, w * scale));
        if (!isFirefox) $(`#${elem.id} .replace`).replaceWith(img);
    };
    img.attr("src", url);
    if (isFirefox) $(`#${elem.id} .replace`).replaceWith(img);
}

function wsConnect() {
    ws = new WebSocket("ws://" + window.location.host + "/websocket");

    ws.onclose = () => {
        if (!iterationFinished) {
            let status = $("#status")[0];
            status.innerText = "Disconnected from the backend.";
            status.style.display = "";
        }
    };

    ws.onerror = ws.onclose;

    ws.onmessage = (e) => {
        let msg = JSON.parse(e.data);

        switch (msg._type) {
        case "Iterate":
            setWithDataURL(msg.image, $("#image")[0]);
            $("#step")[0].innerText = msg.step;
            $("#steps")[0].innerText = msg.steps;
            $("#time")[0].innerText = msg.time.toFixed(2);
            $("#update-size")[0].innerText = msg.update_size.toFixed(2);
            $("#loss")[0].innerText = msg.loss.toExponential(6);
            $("#tv")[0].innerText = msg.tv.toFixed(2);
            $("#status")[0].style.display = "none";
            break;

        case "IterationFinished":
            iterationFinished = true;
            $("#status")[0].innerText = "Iteration finished.";
            $("#status")[0].style.display = "";
            break;
        }
    };

    ws.onopen = () => {
        let status = $("#status")[0];
        status.innerText = "Waiting for the first iteration...";
    };
}

$(document).ready(() => {
    wsConnect();
});
<commit_msg>Use ES6 strict mode<commit_after>"use strict";

let iterationFinished = false;
let ws;

function setWithDataURL(url, elem) {
    let isFirefox = navigator.userAgent.search("Firefox") > -1;
    let img = $("<img>");
    img.attr("class", "replace");
    img[0].onload = () => {
        let h = img[0].naturalHeight;
        let w = img[0].naturalWidth;
        let scale = parseInt($(elem).css("width")) / Math.max(h, w);
        img.attr("height", Math.min(h, h * scale));
        img.attr("width", Math.min(w, w * scale));
        if (!isFirefox) $(`#${elem.id} .replace`).replaceWith(img);
    };
    img.attr("src", url);
    if (isFirefox) $(`#${elem.id} .replace`).replaceWith(img);
}

function wsConnect() {
    ws = new WebSocket("ws://" + window.location.host + "/websocket");

    ws.onclose = () => {
        if (!iterationFinished) {
            let status = $("#status")[0];
            status.innerText = "Disconnected from the backend.";
            status.style.display = "";
        }
    };

    ws.onerror = ws.onclose;

    ws.onmessage = (e) => {
        let msg = JSON.parse(e.data);

        switch (msg._type) {
        case "Iterate":
            setWithDataURL(msg.image, $("#image")[0]);
            $("#step")[0].innerText = msg.step;
            $("#steps")[0].innerText = msg.steps;
            $("#time")[0].innerText = msg.time.toFixed(2);
            $("#update-size")[0].innerText = msg.update_size.toFixed(2);
            $("#loss")[0].innerText = msg.loss.toExponential(6);
            $("#tv")[0].innerText = msg.tv.toFixed(2);
            $("#status")[0].style.display = "none";
            break;

        case "IterationFinished":
            iterationFinished = true;
            $("#status")[0].innerText = "Iteration finished.";
            $("#status")[0].style.display = "";
            break;
        }
    };

    ws.onopen = () => {
        let status = $("#status")[0];
        status.innerText = "Waiting for the first iteration...";
    };
}

$(document).ready(() => {
    wsConnect();
});
<|endoftext|>


<commit_before>google.charts.load('current', {'packages':['line']});
google.charts.setOnLoadCallback(get_data);

function get_data(){
    var data = [[0,   4.2,  6.2,  3.4],
    [1,  37.8, 80.8, 41.8],
    [2,  30.9, 69.5, 32.4],
    [3,  25.4,   57, 25.7],
    [4,  11.7, 18.8, 10.5],
    [5,  11.9, 17.6, 10.4],
    [6,   8.8, 13.6,  7.7],
    [7,   7.6, 12.3,  9.6],
    [8,  12.3, 29.2, 10.6],
    [9,  16.9, 42.9, 14.8],
    [10, 12.8, 30.9, 11.6],
    [11,  5.3,  7.9,  4.7],
    [12,  6.6,  8.4,  5.2],
    [13,  4.8,  6.3,  3.6],
    [14,  4.8,  6.3,  3.6],
    [15,  4.8,  6.3,  3.6],
    [16,  4.8,  6.3,  3.6],
    [17,  4.8,  6.3,  3.6],
    [18,  4.8,  6.3,  3.6],
    [19,  4.8,  6.3,  3.6],
    [20,  4.8,  6.3,  3.6],
    [21,  4.8,  6.3,  3.6],
    [22,  4.8,  6.3,  3.6],
    [23,  4.8,  6.3,  3.6]];
    //do magic.h

    draw_chart(data);
}

function draw_chart(data_array) {

    var data = new google.visualization.DataTable();
    data.addColumn('number', 'Time (hours)');
    data.addColumn('number', 'Weather');
    data.addColumn('number', 'RaspberryTAU');
    data.addColumn('number', 'Titus');

    data.addRows(data_array);

    var options = {
        chart: {
            title: 'Temperature',
            subtitle: 'in degrees celsius'
        },
        //vAxis.minorGridlines.units:
        width: 1500,
        height: 500
    };

    var chart = new google.charts.Line(document.getElementById('chart_div'));

    chart.draw(data, options);

    setTimeout(get_data, 300000); //5 minutes
}
<commit_msg>add base implementation of AJAX functionatily<commit_after>google.charts.load('current', {'packages':['line']});
google.charts.setOnLoadCallback(load_data);

function load_data(){
    var data_file = "TODO: NEED A LINK HERE";
    var http_request = new XMLHttpRequest();
    try{
        // Opera 8.0+, Firefox, Chrome, Safari
        http_request = new XMLHttpRequest();
    }catch (e){
        // Internet Explorer Browsers
        try{
            http_request = new ActiveXObject("Msxml2.XMLHTTP");

        }catch (e) {

            try{
                http_request = new ActiveXObject("Microsoft.XMLHTTP");
            }catch (e){
                // Something went wrong
                alert("Your browser broke!");
                return false;
            }

        }
    }

    http_request.onreadystatechange = function (){
        if (http_request.readyState == 4){
            // Javascript function JSON.parse to parse JSON data
            var jsonObj = JSON.parse(http_request.responseText);
            parse_data(jsonObj);
        }
    }
    http_request.open("GET", data_file, true);
    http_request.send();
}

function parse_data(jsonObj){
    //TODO; DO MAGIC HERE

    draw_chart(data);
}

function draw_chart(data_array) {
    var data = new google.visualization.DataTable();
    data.addColumn('number', 'Time (hours)');
    data.addColumn('number', 'Weather');
    data.addColumn('number', 'RaspberryTAU');
    data.addColumn('number', 'Titus');

    data.addRows(data_array);

    var options = {
        chart: {
            title: 'Temperature',
            subtitle: 'in degrees celsius'
        },
        //vAxis.minorGridlines.units:
        width: 1500,
        height: 500
    };

    var chart = new google.charts.Line(document.getElementById('chart_div'));

    chart.draw(data, options);

    setTimeout(load_data, 300000); //5 minutes
}
<|endoftext|>


<commit_before>/*
 * (c) Copyright Ascensio System SIA 2010-2017
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
 * EU, LV-1021.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

//This file containes definition of object which used in api.js
//It need to prevent minimize the name of object's method.

var Asc;
var AscDFH;
var AscCH;
var AscFormat;
var AscFonts;
var AscCommon;
var AscCommonWord;
var AscCommonExcel;
var AscCommonSlide;

function jq(){}
function DE(){}
function SE(){}
function SSE(){}
function PE(){}
function Ext(){}
function FB(){}
function twttr(){}
function _gaq(){}

function constructor(){}

function touchstart(){}
function touchmove(){}
function touchend(){}
function mousedown(){}
function mousemove(){}
function mouseup(){}
function pointerdown(){}
function pointermove(){}
function pointerup(){}
function MSPointerDown(){}
function MSPointerMove(){}
function MSPointerUp(){}
<commit_msg>add editor to unminimized variables<commit_after>/*
 * (c) Copyright Ascensio System SIA 2010-2017
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
 * EU, LV-1021.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

//This file containes definition of object which used in api.js
//It need to prevent minimize the name of object's method.

var Asc;
var AscDFH;
var AscCH;
var AscFormat;
var AscFonts;
var AscCommon;
var AscCommonWord;
var AscCommonExcel;
var AscCommonSlide;

function jq(){}
function DE(){}
function SE(){}
function SSE(){}
function PE(){}
function Ext(){}
function FB(){}
function twttr(){}
function _gaq(){}

function constructor(){}

function touchstart(){}
function touchmove(){}
function touchend(){}
function mousedown(){}
function mousemove(){}
function mouseup(){}
function pointerdown(){}
function pointermove(){}
function pointerup(){}
function MSPointerDown(){}
function MSPointerMove(){}
function MSPointerUp(){}

var editor;
<|endoftext|>


<commit_before>export default {
  errors: {
    auth: {
      invalidCredentials: 'Invalid credentials',
      invalidResetToken: 'Expired or invalid token',
      paymentError: 'Payment error',
      paymentMethodError: 'Invalid payment method error',
      registrationLimitExceeded: 'Registration limit was exceeded',
      userExistsAlready: 'A user with this email address already exists',
      userNotExisting: 'User does not exist',
    },
    meeting: {
      alreadyJoined: 'You already joined a meeting during this date',
      invalidDate: 'The date is too soon or already in the past',
      noPlaceFound: 'Sorry, we could not find a place for you during the given date',
    },
  },
  activityMails: {
    RECEIVED_MESSAGE: {
      subject: 'YOU RECEIVED A NEW MESSAGE',
      message: '{name} sent you a message on the platform!',
    },
    RECEIVED_REQUEST: {
      subject: 'YOU RECEIVED A REQUEST',
      message: '{name} requests your {objectType} "{objectTitle}" for an event!',
    },
    CREATE_RANDOM_MEETING: {
      subject: 'YOU CREATED A RANDOM MEETING',
      message: 'You created a random meeting! You can wait now for other participants who might join in!\n\nA conversation related to this random meeting was just openend in your inbox, check it on the platform.\n\nThere you can start the dialogue with other attending participants you might eventually meet soon <3',
    },
    JOIN_RANDOM_MEETING: {
      subject: 'YOU JOINED A RANDOM MEETING',
      message: 'You joined a random meeting!\n\nA conversation related to this random meeting was just openend in your inbox, check it on the platform for further details like time and place.\n\nThere you can start the dialogue with other attending participants you might eventually meet soon <3',
    },
  },
  products: {
    participation: 'Participation fee',
    ticket: 'Festival ticket',
  },
  meeting: {
    createMessageText: '{name} created a random meeting at {placeTitle} on {date}. This conversation is your tool where you can prepare and organize yourself for the final rendevous. Drink a beer, start a band or a revolution, have a passionate debate or fall in love with each other - even in virtual places.',
    createMessageTitle: 'Random meeting {date} @ {placeTitle}',
    joinMessageText: '{name} joins the random meeting!',
  },
}
<commit_msg>Correct the french<commit_after>export default {
  errors: {
    auth: {
      invalidCredentials: 'Invalid credentials',
      invalidResetToken: 'Expired or invalid token',
      paymentError: 'Payment error',
      paymentMethodError: 'Invalid payment method error',
      registrationLimitExceeded: 'Registration limit was exceeded',
      userExistsAlready: 'A user with this email address already exists',
      userNotExisting: 'User does not exist',
    },
    meeting: {
      alreadyJoined: 'You already joined a meeting during this date',
      invalidDate: 'The date is too soon or already in the past',
      noPlaceFound: 'Sorry, we could not find a place for you during the given date',
    },
  },
  activityMails: {
    RECEIVED_MESSAGE: {
      subject: 'YOU RECEIVED A NEW MESSAGE',
      message: '{name} sent you a message on the platform!',
    },
    RECEIVED_REQUEST: {
      subject: 'YOU RECEIVED A REQUEST',
      message: '{name} requests your {objectType} "{objectTitle}" for an event!',
    },
    CREATE_RANDOM_MEETING: {
      subject: 'YOU CREATED A RANDOM MEETING',
      message: 'You created a random meeting! You can wait now for other participants who might join in!\n\nA conversation related to this random meeting was just openend in your inbox, check it on the platform.\n\nThere you can start the dialogue with other attending participants you might eventually meet soon <3',
    },
    JOIN_RANDOM_MEETING: {
      subject: 'YOU JOINED A RANDOM MEETING',
      message: 'You joined a random meeting!\n\nA conversation related to this random meeting was just openend in your inbox, check it on the platform for further details like time and place.\n\nThere you can start the dialogue with other attending participants you might eventually meet soon <3',
    },
  },
  products: {
    participation: 'Participation fee',
    ticket: 'Festival ticket',
  },
  meeting: {
    createMessageText: '{name} created a random meeting at {placeTitle} on {date}. This conversation is your tool where you can prepare and organize yourself for the final rendezvous. Drink a beer, start a band or a revolution, have a passionate debate or fall in love with each other - even in virtual places.',
    createMessageTitle: 'Random meeting {date} @ {placeTitle}',
    joinMessageText: '{name} joins the random meeting!',
  },
}
<|endoftext|>


<commit_before>var fs = require('fs');
var files = require('./files');
var index = require('./index');
var objects = require('./objects');
var refs = require('./refs');
var diff = require('./diff');
var util = require('./util');

var gimletApi = module.exports = {
  init: function(_) {
    if (files.inRepo()) { return; }

    files.writeFilesFromTree({
      ".gimlet": {
        HEAD: "ref: refs/heads/master\n",
        objects: {},
        refs: {
          heads: {},
          remotes: {
            origin: {}
          },
        }
      }
    });
  },

  add: function(path, _) {
    files.assertInRepo();

    var addedFiles = files.lsRecursive(path);
    if (addedFiles.length === 0) {
      throw "fatal: pathspec '" + files.pathFromRepoRoot(path) + "' did not match any files";
    } else {
      for (var i = 0; i < addedFiles.length; i++) {
        this.update_index(addedFiles[i], { add: true });
      }
    }
  },

  update_index: function(path, opts) {
    files.assertInRepo();
    opts = opts || {};

    var pathFromRoot = files.pathFromRepoRoot(path)
    if (!fs.existsSync(path)) {
      throw "error: " + pathFromRoot + ": does not exist\n" +
        "fatal: Unable to process path " + pathFromRoot;
    } else if (fs.statSync(path).isDirectory()) {
      throw "error: " + pathFromRoot + ": is a directory - add files inside instead\n" +
        "fatal: Unable to process path " + pathFromRoot;
    } else if (!index.readHasFile(path) && opts.add === undefined) {
      throw "error: " + pathFromRoot  +
        ": cannot add to the index - missing --add option?\n" +
        "fatal: Unable to process path " + pathFromRoot;
    } else {
      index.writeFile(path);
    }
  },

  hash_object: function(file, opts) {
    files.assertInRepo();
    opts = opts || {};

    if (!fs.existsSync(file)) {
      throw "fatal: Cannot open '" + file + "': No such file or directory"
    } else {
      var fileContents = files.read(file);
      if (opts.w) {
        return objects.write(fileContents);
      }

      return util.hash(fileContents);
    }
  },

  write_tree: function(_) {
    files.assertInRepo();
    return objects.writeTree(files.nestFlatTree(index.read()));
  },

  commit: function(opts) {
    files.assertInRepo();

    if (Object.keys(index.read()).length === 0) {
      throw "# On branch master\n#\n# Initial commit\n#\n" +
        "nothing to commit (create/copy files and use 'git add' to track)";
    } else {
      var headHash = refs.readHash("HEAD");
      var treeHash = this.write_tree();

      if (headHash !== undefined &&
          treeHash === objects.treeHash(objects.read(headHash))) {
        throw "# On " + refs.readCurrentBranchName() + "\n" +
          "nothing to commit, working directory clean";
      } else {
        var isFirstCommit = refs.readHash("HEAD") === undefined;
        var parentHashes = isFirstCommit ? [] : [refs.readHash("HEAD")];
        var commmitHash = objects.write(objects.composeCommit(treeHash, opts.m, parentHashes));
        this.update_ref("HEAD", commmitHash);
        return "[" + refs.readCurrentBranchName() + " " + commmitHash + "] " + opts.m;
      }
    }
  },

  branch: function(name, _) {
    files.assertInRepo();

    if (name === undefined) {
      return refs.readLocalHeads().map(function(branchName) {
        var marker = branchName === refs.readCurrentBranchName() ? "* " : "  ";
        return marker + branchName;
      }).join("\n") + "\n";
    } else if (refs.readHash("HEAD") === undefined) {
      throw "fatal: Not a valid object name: '" + refs.readCurrentBranchName() + "'.";
    } else {
      refs.write(refs.nameToBranchRef(name), refs.readHash("HEAD"));
    }
  },

  update_ref: function(refToUpdate, refToUpdateTo, _) {
    files.assertInRepo();

    if (!refs.isRef(refToUpdate)) {
      throw "fatal: Cannot lock the ref " + refToUpdate + ".";
    } else {
      var hash = objects.read(refToUpdateTo) ? refToUpdateTo : refs.readHash(refToUpdateTo);
      if (!objects.readExists(hash)) {
        throw "fatal: " + refToUpdateTo + ": not a valid SHA1";
      } else if (!(objects.type(objects.read(hash)) === "commit")) {
        throw "error: Trying to write non-commit object " + hash + " to branch " +
          refs.readTerminalRef(refToUpdate) + "\n" +
          "fatal: Cannot update the ref " + refToUpdate;
      } else {
        refs.write(refs.readTerminalRef(refToUpdate), hash);
      }
    }
  },

  checkout: function(ref, _) {
    files.assertInRepo();

    if (!objects.readExists(hash)) {
      throw "error: pathspec " + ref + " did not match any file(s) known to git."
    }
  },

  diff: function(ref1, ref2, opts) {
    files.assertInRepo();

    var hash1 = objects.read(ref1) ? ref1 : refs.readHash(ref1);
    var hash2 = objects.read(ref2) ? ref2 : refs.readHash(ref2);

    if (ref1 !== undefined && hash1 === undefined) {
      throw "fatal: ambiguous argument " + ref1 + ": unknown revision";
    } else if (ref2 !== undefined && hash2 === undefined) {
      throw "fatal: ambiguous argument " + ref2 + ": unknown revision";
    } else {
      if (opts["name-status"] !== true) {
        throw "unsupported"; // for now
      } else {
        if (ref1 === undefined && ref2 === undefined) {
          return diff.toString(diff.nameStatus(index.read(), index.readWorkingCopyIndex()));
        }
      }
    }
  }
};
<commit_msg>Add first run at diff(ref, undefined)<commit_after>var fs = require('fs');
var files = require('./files');
var index = require('./index');
var objects = require('./objects');
var refs = require('./refs');
var diff = require('./diff');
var util = require('./util');

var gimletApi = module.exports = {
  init: function(_) {
    if (files.inRepo()) { return; }

    files.writeFilesFromTree({
      ".gimlet": {
        HEAD: "ref: refs/heads/master\n",
        objects: {},
        refs: {
          heads: {},
          remotes: {
            origin: {}
          },
        }
      }
    });
  },

  add: function(path, _) {
    files.assertInRepo();

    var addedFiles = files.lsRecursive(path);
    if (addedFiles.length === 0) {
      throw "fatal: pathspec '" + files.pathFromRepoRoot(path) + "' did not match any files";
    } else {
      for (var i = 0; i < addedFiles.length; i++) {
        this.update_index(addedFiles[i], { add: true });
      }
    }
  },

  update_index: function(path, opts) {
    files.assertInRepo();
    opts = opts || {};

    var pathFromRoot = files.pathFromRepoRoot(path)
    if (!fs.existsSync(path)) {
      throw "error: " + pathFromRoot + ": does not exist\n" +
        "fatal: Unable to process path " + pathFromRoot;
    } else if (fs.statSync(path).isDirectory()) {
      throw "error: " + pathFromRoot + ": is a directory - add files inside instead\n" +
        "fatal: Unable to process path " + pathFromRoot;
    } else if (!index.readHasFile(path) && opts.add === undefined) {
      throw "error: " + pathFromRoot  +
        ": cannot add to the index - missing --add option?\n" +
        "fatal: Unable to process path " + pathFromRoot;
    } else {
      index.writeFile(path);
    }
  },

  hash_object: function(file, opts) {
    files.assertInRepo();
    opts = opts || {};

    if (!fs.existsSync(file)) {
      throw "fatal: Cannot open '" + file + "': No such file or directory"
    } else {
      var fileContents = files.read(file);
      if (opts.w) {
        return objects.write(fileContents);
      }

      return util.hash(fileContents);
    }
  },

  write_tree: function(_) {
    files.assertInRepo();
    return objects.writeTree(files.nestFlatTree(index.read()));
  },

  commit: function(opts) {
    files.assertInRepo();

    if (Object.keys(index.read()).length === 0) {
      throw "# On branch master\n#\n# Initial commit\n#\n" +
        "nothing to commit (create/copy files and use 'git add' to track)";
    } else {
      var headHash = refs.readHash("HEAD");
      var treeHash = this.write_tree();

      if (headHash !== undefined &&
          treeHash === objects.treeHash(objects.read(headHash))) {
        throw "# On " + refs.readCurrentBranchName() + "\n" +
          "nothing to commit, working directory clean";
      } else {
        var isFirstCommit = refs.readHash("HEAD") === undefined;
        var parentHashes = isFirstCommit ? [] : [refs.readHash("HEAD")];
        var commmitHash = objects.write(objects.composeCommit(treeHash, opts.m, parentHashes));
        this.update_ref("HEAD", commmitHash);
        return "[" + refs.readCurrentBranchName() + " " + commmitHash + "] " + opts.m;
      }
    }
  },

  branch: function(name, _) {
    files.assertInRepo();

    if (name === undefined) {
      return refs.readLocalHeads().map(function(branchName) {
        var marker = branchName === refs.readCurrentBranchName() ? "* " : "  ";
        return marker + branchName;
      }).join("\n") + "\n";
    } else if (refs.readHash("HEAD") === undefined) {
      throw "fatal: Not a valid object name: '" + refs.readCurrentBranchName() + "'.";
    } else {
      refs.write(refs.nameToBranchRef(name), refs.readHash("HEAD"));
    }
  },

  update_ref: function(refToUpdate, refToUpdateTo, _) {
    files.assertInRepo();

    if (!refs.isRef(refToUpdate)) {
      throw "fatal: Cannot lock the ref " + refToUpdate + ".";
    } else {
      var hash = objects.read(refToUpdateTo) ? refToUpdateTo : refs.readHash(refToUpdateTo);
      if (!objects.readExists(hash)) {
        throw "fatal: " + refToUpdateTo + ": not a valid SHA1";
      } else if (!(objects.type(objects.read(hash)) === "commit")) {
        throw "error: Trying to write non-commit object " + hash + " to branch " +
          refs.readTerminalRef(refToUpdate) + "\n" +
          "fatal: Cannot update the ref " + refToUpdate;
      } else {
        refs.write(refs.readTerminalRef(refToUpdate), hash);
      }
    }
  },

  checkout: function(ref, _) {
    files.assertInRepo();

    if (!objects.readExists(hash)) {
      throw "error: pathspec " + ref + " did not match any file(s) known to git."
    }
  },

  diff: function(ref1, ref2, opts) {
    files.assertInRepo();

    var hash1 = objects.read(ref1) ? ref1 : refs.readHash(ref1);
    var hash2 = objects.read(ref2) ? ref2 : refs.readHash(ref2);

    if (ref1 !== undefined && hash1 === undefined) {
      throw "fatal: ambiguous argument " + ref1 + ": unknown revision";
    } else if (ref2 !== undefined && hash2 === undefined) {
      throw "fatal: ambiguous argument " + ref2 + ": unknown revision";
    } else {
      if (opts["name-status"] !== true) {
        throw "unsupported"; // for now
      } else {
        if (ref1 === undefined && ref2 === undefined) {
          return diff.toString(diff.nameStatus(index.read(), index.readWorkingCopyIndex()));
        } else if (ref2 === undefined) {
          return diff.toString(diff.nameStatus(diff.readCommitIndex(hash1),
                                               index.readWorkingCopyIndex()));
        }
      }
    }
  }
};
<|endoftext|>


<commit_before>/*
  @method gitPull
  Takes a flightplan instance and transport
  @param remote {Object} Flightplan transport instance
  @param webRoot {string} path to run git pull on the remote server
   e.g. /var/www/project
*/
var gitPull = function (remote, webRoot) {
  // git pull
  remote.with('cd ' + webRoot, function() {
    remote.exec('git pull');
  });
};

/*
  @method addToPlan
  Takes a flightplan instance and adds a task called gitDeploy
  @param plan {Object} Flightplan instance
  @param plan.runtime.options.webRoot {String} Remote path to run git pull
*/
var addToPlan = function (plan) {
  // Plan to git deploy via ssh remote
  var webRoot = plan.runtime.options.webRoot;

  plan.remote('gitDeploy', function(remote) {
    remoteGitDeploy(remote, webRoot);
  });
};

module.exports = {
  addToPlan: addToPlan,
  remoteGitDeploy: remoteGitDeploy
};
<commit_msg>Fix unchanged function name for gitPull<commit_after>/*
  @method gitPull
  Takes a flightplan instance and transport
  @param remote {Object} Flightplan transport instance
  @param webRoot {string} path to run git pull on the remote server
   e.g. /var/www/project
*/
var gitPull = function (remote, webRoot) {
  // git pull
  remote.with('cd ' + webRoot, function() {
    remote.exec('git pull');
  });
};

/*
  @method addToPlan
  Takes a flightplan instance and adds a task called gitDeploy
  @param plan {Object} Flightplan instance
  @param plan.runtime.options.webRoot {String} Remote path to run git pull
*/
var addToPlan = function (plan) {
  // Plan to git deploy via ssh remote
  var webRoot = plan.runtime.options.webRoot;

  plan.remote('gitDeploy', function(remote) {
    gitPull(remote, webRoot);
  });
};

module.exports = {
  addToPlan: addToPlan,
  gitPull: gitPull
};
<|endoftext|>


<commit_before>'use strict';
const Random = require('./random');

function state() {
  return require('./state');
}

class Goat {
  constructor (constraint, x, y) {
    this.id = Math.random();
    this.position = {
      x: x,
      y: y,
    };
    this.stomach = 5;
    this.formerPosition = {};
    this.constraint = constraint;
  }

  reproduce(cell) {
    const world = state();
    let goatX;
    if (cell.x !== 0) {
      goatX = cell.x - 1;
    } else {
      goatX = cell.x + 1;
    }
    const newGoat = new Goat(this.constraint, goatX, cell.y);
    world.board.goats[newGoat.id] = newGoat;
  }

  eat(cell) {
    this.stomach += cell.grass;
    if (this.stomach >= 10) {
      this.stomach = 5;
      this.reproduce(cell);
    }
    // console.log(`goat ${this.id} has eaten ${this.stomach}`);
    cell.grass = 0;
  }

  metabolize() {
    this.stomach -= 0.1;
    if (this.stomach <= 0) {
      delete(state().board.goats[this.id]);
    }
  }

  move() {
    this.formerPosition.x = this.position.x;
    this.formerPosition.y = this.position.y;
    this.position.x += Random.intFromInterval(-1, 1);
    this.position.y += Random.intFromInterval(-1, 1);
    if (this.position.x <= 0) {
      this.position.x++;
    }
    if (this.position.y <= 0) {
      this.position.y++;
    }
    if (this.position.x >= this.constraint) {
      this.position.x--;
    }
    if (this.position.y >= this.constraint) {
      this.position.y--;
    }
    this.metabolize();
    if (this.formerPosition.x !== this.position.x || this.formerPosition.y !== this.position.y || this.stomach <= 0) {
      return this;
    }
  }
}

module.exports = Goat;
<commit_msg>Makes goats a bit less frisky<commit_after>'use strict';
const Random = require('./random');

function state() {
  return require('./state');
}

class Goat {
  constructor (constraint, x, y) {
    this.id = Math.random();
    this.position = {
      x: x,
      y: y,
    };
    this.stomach = 5;
    this.formerPosition = {};
    this.constraint = constraint;
  }

  reproduce(cell) {
    const world = state();
    let goatX;
    if (cell.x !== 0) {
      goatX = cell.x - 1;
    } else {
      goatX = cell.x + 1;
    }
    const newGoat = new Goat(this.constraint, goatX, cell.y);
    world.board.goats[newGoat.id] = newGoat;
  }

  eat(cell) {
    this.stomach += cell.grass;
    if (this.stomach >= 20) {
      this.stomach = 5;
      this.reproduce(cell);
    }
    // console.log(`goat ${this.id} has eaten ${this.stomach}`);
    cell.grass = 0;
  }

  metabolize() {
    this.stomach -= 0.1;
    if (this.stomach <= 0) {
      delete(state().board.goats[this.id]);
    }
  }

  move() {
    this.formerPosition.x = this.position.x;
    this.formerPosition.y = this.position.y;
    this.position.x += Random.intFromInterval(-1, 1);
    this.position.y += Random.intFromInterval(-1, 1);
    if (this.position.x <= 0) {
      this.position.x++;
    }
    if (this.position.y <= 0) {
      this.position.y++;
    }
    if (this.position.x >= this.constraint) {
      this.position.x--;
    }
    if (this.position.y >= this.constraint) {
      this.position.y--;
    }
    this.metabolize();
    if (this.formerPosition.x !== this.position.x || this.formerPosition.y !== this.position.y || this.stomach <= 0) {
      return this;
    }
  }
}

module.exports = Goat;
<|endoftext|>


<commit_before>// @flow
import { Card, Button, Tooltip, Icon } from "antd";
import Markdown from "react-remarkable";
import * as React from "react";
import classNames from "classnames";
import { Link } from "react-router-dom";

import type { APIDataset, APIDatasetDetails } from "admin/api_flow_types";
import { formatScale } from "libs/format_utils";
import {
  getThumbnailURL,
  hasSegmentation,
  getSegmentationThumbnailURL,
  getDatasetExtentAsString,
} from "oxalis/model/accessors/dataset_accessor";
import { compareBy } from "libs/utils";

type ExtendedDatasetDetails = { ...APIDatasetDetails, name: string, scale: string, extent: string };

const thumbnailDimension = 500;
const miniThumbnailDimension = 75;
const blacklistedSegmentationNames = ["2012-09-28_ex145_07x2_ROI2017"];

function getDisplayName(dataset: APIDataset): string {
  return dataset.displayName != null && dataset.displayName !== ""
    ? dataset.displayName
    : dataset.name;
}

function getDetails(dataset: APIDataset): ExtendedDatasetDetails {
  const { dataSource, details } = dataset;
  return {
    ...details,
    scale: formatScale(dataSource.scale, 0),
    name: getDisplayName(dataset),
    extent: getDatasetExtentAsString(dataset, false),
  };
}

function ThumbnailOverlay({ details }) {
  return (

        {details.species && (

            {details.species}

        )}
        {details.brainRegion && (

            {details.brainRegion}

        )}

        {details.name}

{details.acquisition}

          {details.scale}

          {details.extent}

  );
}

function PublishedDatasetsOverlay({ datasets, activeDataset, setActiveDataset }) {
  return (

          {datasets.map(dataset => {
            const datasetIdString = `${dataset.owningOrganization}/${dataset.name}`;
            return (
              <Link to={`/datasets/${datasetIdString}/view`} key={datasetIdString}>

                  <Button
                    className={classNames("mini-dataset-thumbnail", {
                      active: dataset.name === activeDataset.name,
                    })}
                    title="Click To View"
                    style={{
                      background: `url('${getThumbnailURL(
                        dataset,
                      )}?w=${miniThumbnailDimension}&h=${miniThumbnailDimension}')`,
                      width: `${miniThumbnailDimension}px`,
                      height: `${miniThumbnailDimension}px`,
                    }}
                    onMouseEnter={() => setActiveDataset(dataset)}
                  >
                    {!blacklistedSegmentationNames.includes(dataset.name) && (

                    )}
                  </Button>

              </Link>
            );
          })}

  );
}

const typeHint: Array<APIDataset> = [];

type Props = { datasets: Array<APIDataset>, showDetailedLink: boolean };
type State = { activeDataset: APIDataset };

class PublicationCard extends React.PureComponent<Props, State> {
  state = {
    activeDataset: this.props.datasets[0],
  };

  render() {
    const { datasets, showDetailedLink } = this.props;
    const { activeDataset } = this.state;
    const { publication } = activeDataset;
    // This method will only be called for datasets with a publication, but Flow doesn't know that
    if (publication == null) throw Error("Assertion Error: Dataset has no associated publication.");

    const sortedDatasets = datasets.sort(compareBy(typeHint, dataset => dataset.sortingKey));

    const setActiveDataset = dataset => this.setState({ activeDataset: dataset });

    const thumbnailURL = getThumbnailURL(activeDataset);
    const segmentationThumbnailURL = hasSegmentation(activeDataset)
      ? getSegmentationThumbnailURL(activeDataset)
      : null;
    const details = getDetails(activeDataset);

    return (
      <Card bodyStyle={{ padding: 0 }} className="spotlight-item-card" bordered={false}>

              {publication.title}
              {showDetailedLink ? (
                <Link to={`/publication/${publication.id}`}>
                  <Tooltip title="Open permalink">
                    <Icon
                      type="link"
                      style={{
                        fontSize: 16,
                        color: "#555",
                        marginBottom: 18,
                        marginLeft: 8,
                      }}
                      className="hidden-icon"
                    />
                  </Tooltip>
                </Link>
              ) : null}

              <Markdown
                source={publication.description}
                options={{ html: false, breaks: true, linkify: true }}
              />

              <Link
                to={`/datasets/${activeDataset.owningOrganization}/${activeDataset.name}/view`}
                className="absolute"
              >

Click To View

              </Link>
              <div
                className="dataset-thumbnail-image absolute"
                style={{
                  backgroundImage: `url('${thumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`,
                }}
              />
              {!blacklistedSegmentationNames.includes(activeDataset.name) &&
              segmentationThumbnailURL ? (
                <div
                  className="dataset-thumbnail-image absolute segmentation"
                  style={{
                    backgroundImage: `url('${segmentationThumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`,
                  }}
                />
              ) : null}
              <ThumbnailOverlay details={details} />
              {sortedDatasets.length > 1 && (
                <PublishedDatasetsOverlay
                  datasets={sortedDatasets}
                  activeDataset={activeDataset}
                  setActiveDataset={setActiveDataset}
                />
              )}

      </Card>
    );
  }
}

export default PublicationCard;
<commit_msg>specifies initial selected dataset in publication tile (#4828)<commit_after>// @flow
import { Card, Button, Tooltip, Icon } from "antd";
import Markdown from "react-remarkable";
import React, { useState } from "react";
import classNames from "classnames";
import { Link } from "react-router-dom";

import type { APIDataset, APIDatasetDetails } from "admin/api_flow_types";
import { formatScale } from "libs/format_utils";
import {
  getThumbnailURL,
  hasSegmentation,
  getSegmentationThumbnailURL,
  getDatasetExtentAsString,
} from "oxalis/model/accessors/dataset_accessor";
import { compareBy } from "libs/utils";

type ExtendedDatasetDetails = { ...APIDatasetDetails, name: string, scale: string, extent: string };

const thumbnailDimension = 500;
const miniThumbnailDimension = 75;
const blacklistedSegmentationNames = ["2012-09-28_ex145_07x2_ROI2017"];

function getDisplayName(dataset: APIDataset): string {
  return dataset.displayName != null && dataset.displayName !== ""
    ? dataset.displayName
    : dataset.name;
}

function getDetails(dataset: APIDataset): ExtendedDatasetDetails {
  const { dataSource, details } = dataset;
  return {
    ...details,
    scale: formatScale(dataSource.scale, 0),
    name: getDisplayName(dataset),
    extent: getDatasetExtentAsString(dataset, false),
  };
}

function ThumbnailOverlay({ details }) {
  return (

        {details.species && (

            {details.species}

        )}
        {details.brainRegion && (

            {details.brainRegion}

        )}

        {details.name}

{details.acquisition}

          {details.scale}

          {details.extent}

  );
}

function PublishedDatasetsOverlay({ datasets, activeDataset, setActiveDataset }) {
  return (

          {datasets.map(dataset => {
            const datasetIdString = `${dataset.owningOrganization}/${dataset.name}`;
            return (
              <Link to={`/datasets/${datasetIdString}/view`} key={datasetIdString}>

                  <Button
                    className={classNames("mini-dataset-thumbnail", {
                      active: dataset.name === activeDataset.name,
                    })}
                    title="Click To View"
                    style={{
                      background: `url('${getThumbnailURL(
                        dataset,
                      )}?w=${miniThumbnailDimension}&h=${miniThumbnailDimension}')`,
                      width: `${miniThumbnailDimension}px`,
                      height: `${miniThumbnailDimension}px`,
                    }}
                    onMouseEnter={() => setActiveDataset(dataset)}
                  >
                    {!blacklistedSegmentationNames.includes(dataset.name) && (

                    )}
                  </Button>

              </Link>
            );
          })}

  );
}

const typeHint: Array<APIDataset> = [];

type Props = { datasets: Array<APIDataset>, showDetailedLink: boolean };

function PublicationCard({ datasets, showDetailedLink }: Props) {
  const sortedDatasets = datasets.sort(compareBy(typeHint, dataset => dataset.sortingKey));
  const [activeDataset, setActiveDataset] = useState<APIDataset>(sortedDatasets[0]);

  const { publication } = activeDataset;
  // This method will only be called for datasets with a publication, but Flow doesn't know that
  if (publication == null) throw Error("Assertion Error: Dataset has no associated publication.");

  const thumbnailURL = getThumbnailURL(activeDataset);
  const segmentationThumbnailURL = hasSegmentation(activeDataset)
    ? getSegmentationThumbnailURL(activeDataset)
    : null;
  const details = getDetails(activeDataset);

  return (
    <Card bodyStyle={{ padding: 0 }} className="spotlight-item-card" bordered={false}>

            {publication.title}
            {showDetailedLink ? (
              <Link to={`/publication/${publication.id}`}>
                <Tooltip title="Open permalink">
                  <Icon
                    type="link"
                    style={{
                      fontSize: 16,
                      color: "#555",
                      marginBottom: 18,
                      marginLeft: 8,
                    }}
                    className="hidden-icon"
                  />
                </Tooltip>
              </Link>
            ) : null}

            <Markdown
              source={publication.description}
              options={{ html: false, breaks: true, linkify: true }}
            />

            <Link
              to={`/datasets/${activeDataset.owningOrganization}/${activeDataset.name}/view`}
              className="absolute"
            >

Click To View

            </Link>
            <div
              className="dataset-thumbnail-image absolute"
              style={{
                backgroundImage: `url('${thumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`,
              }}
            />
            {!blacklistedSegmentationNames.includes(activeDataset.name) &&
            segmentationThumbnailURL ? (
              <div
                className="dataset-thumbnail-image absolute segmentation"
                style={{
                  backgroundImage: `url('${segmentationThumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`,
                }}
              />
            ) : null}
            <ThumbnailOverlay details={details} />
            {sortedDatasets.length > 1 && (
              <PublishedDatasetsOverlay
                datasets={sortedDatasets}
                activeDataset={activeDataset}
                setActiveDataset={setActiveDataset}
              />
            )}

    </Card>
  );
}

export default PublicationCard;
<|endoftext|>


<commit_before>'use strict';

import React from 'react';

const limitArr = [5, 10, 25, 50, 'all'];

class RepoTable extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      repos: this.props.repos,
      offset: 0,
      limit: 5
    };
    // NOTE: these can all probably be put into one big event listener
    this.changeOffSet = this.changeOffSet.bind(this);
    this.changeLimit = this.changeLimit.bind(this);
    this.sortByName = this.sortByName.bind(this);
    this.sortByDateCreated = this.sortByDateCreated.bind(this);
    this.sortByDateUpdated = this.sortByDateUpdated.bind(this);
    this.sortBySize = this.sortBySize.bind(this);
  }

  changeOffSet(e){
    let offset = parseInt(this.state.offset);
    let limit = parseInt(this.state.limit);
    if(e.target.textContent === '<'){
      if(offset - limit > 0){
        this.setState({offset: offset - limit});
      }else{
        this.setState({offset: 0});
      }
    }else{
      if(offset + limit < this.state.repos.length - limit){
        this.setState({offset: offset + limit});
      }else{
        this.setState({offset: this.state.repos.length - limit});
      }
    }
  }

  changeLimit(e){
    let offset = parseInt(this.state.offset);
    let selected = e.target.options[e.target.selectedIndex].value;
    if(selected === 'all'){
      this.setState({offset: 0, limit: this.state.repos.length});
    }else{
      this.setState({limit: selected});
    }
    if(parseInt(selected) + offset > this.state.repos.length){
      this.setState({offset: this.state.repos.length - parseInt(selected)});
    }
  }

  sortByName(e){

  }

  sortByDateCreated(e){

  }

  sortByDateUpdated(e){

  }

  sortBySize(e){

  }

  render(){
    let offset = parseInt(this.state.offset);
    let limit = parseInt(this.state.limit);
    return(

Repository Table

 |

{`${offset} - ${offset + limit}`}

            <button onClick={this.changeOffSet}>{'<'}</button>
            <button onClick={this.changeOffSet}>{'>'}</button>

items per page

            <select onChange={this.changeLimit}>
              {limitArr.map((ele, index) => {
                return(<option key={index} value={ele}>{ele}</option>);
              })}
            </select>

              <button onClick={this.sortByName}>name</button>
              <button onClick={this.sortByName}>language</button>
              <button onClick={this.sortByName}>forks</button>
              <button onClick={this.sortByName}>watchers</button>
              <button onClick={this.sortByDateCreated}>date created</button>
              <button onClick={this.sortByDateUpdated}>last update</button>
              <button onClick={this.sortBySize}>size</button>

            {this.state.repos.slice(offset, (offset + limit)).map((ele, index) => {
              return(

                  {ele.name}
                  {ele.language}
                  {ele.forks}
                  {ele.watchers}
                  {new Date(ele.created_at).toString()}
                  {new Date(ele.updated_at).toString()}
                  {ele.size}

              );
            })}

    );
  }
}

export default RepoTable;
<commit_msg>basic sorting brought in from previous project I worked on, not coppied<commit_after>'use strict';

import React from 'react';

const limitArr = [5, 10, 25, 50, 'all'];

class RepoTable extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      repos: this.props.repos,
      offset: 0,
      limit: 5
    };

    this.changeOffSet = this.changeOffSet.bind(this);
    this.changeLimit = this.changeLimit.bind(this);
    this.sortBy = this.sortBy.bind(this);
  }

  changeOffSet(e){
    let offset = parseInt(this.state.offset);
    let limit = parseInt(this.state.limit);
    if(e.target.textContent === '<'){
      if(offset - limit > 0){
        this.setState({offset: offset - limit});
      }else{
        this.setState({offset: 0});
      }
    }else{
      if(offset + limit < this.state.repos.length - limit){
        this.setState({offset: offset + limit});
      }else{
        this.setState({offset: this.state.repos.length - limit});
      }
    }
  }

  changeLimit(e){
    let offset = parseInt(this.state.offset);
    let selected = e.target.options[e.target.selectedIndex].value;
    if(selected === 'all'){
      this.setState({offset: 0, limit: this.state.repos.length});
    }else{
      this.setState({limit: selected});
    }
    if(parseInt(selected) + offset > this.state.repos.length){
      this.setState({offset: this.state.repos.length - parseInt(selected)});
    }
  }

  sortBy(e){
    console.log(e.target);
    this.setState({repos: this.state.repos.sort((a, b) => {
      let sorting = e.target.value.split(' ').join('');
      if(a[sorting]  b[sorting]){
        return 1;
      }else{
        return 0;
      }
    })});
  }

  render(){
    let offset = parseInt(this.state.offset);
    let limit = parseInt(this.state.limit);
    return(

Repository Table

 |

{`${offset} - ${offset + limit}`}

            <button onClick={this.changeOffSet}>{'<'}</button>
            <button onClick={this.changeOffSet}>{'>'}</button>

items per page

            <select onChange={this.changeLimit}>
              {limitArr.map((ele, index) => {
                return(<option key={index} value={ele}>{ele}</option>);
              })}
            </select>

              <button value="name" onClick={this.sortBy}>name</button>
              <button value="language" onClick={this.sortBy}>language</button>
              <button value="forks" onClick={this.sortBy}>forks</button>
              <button value="watchers" onClick={this.sortBy}>watchers</button>
              <button value="date created" onClick={this.sortBy}>date created</button>
              <button value="last update" onClick={this.sortBy}>last update</button>
              <button value="size" onClick={this.sortBy}>size</button>

            {this.state.repos.slice(offset, (offset + limit)).map((ele, index) => {
              return(

                  {ele.name}
                  {ele.language}
                  {ele.forks}
                  {ele.watchers}
                  {new Date(ele.created_at).toString()}
                  {new Date(ele.updated_at).toString()}
                  {ele.size}

              );
            })}

    );
  }
}

export default RepoTable;
<|endoftext|>


<commit_before>"use strict";

class GSUI {
	static $noop() {}
	static $popup = document.createElement( "gsui-popup" );
	static $dragshield = document.createElement( "gsui-dragshield" );

	// .........................................................................
	static $clamp( n, min, max ) {
		return (
			min < max
				? Math.max( min, Math.min( n || 0, max ) )
				: Math.max( max, Math.min( n || 0, min ) )
		);
	}

	// .........................................................................
	static $diffObjects( a, b ) {
		let empty = true;
		const diff = Object.entries( b ).reduce( ( diff, [ bk, bv ] ) => {
			const av = a[ bk ];
			const newval = av === bv ? undefined :
				typeof bv !== "object" || bv === null ? bv :
				typeof av !== "object" || av === null
					? Object.freeze( JSON.parse( JSON.stringify( bv ) ) )
					: GSUI.$diffObjects( av, bv );

			if ( newval !== undefined ) {
				empty = false;
				diff[ bk ] = newval;
			}
			return diff;
		}, {} );

		Object.keys( a ).forEach( ak => {
			if ( !( ak in b ) ) {
				empty = false;
				diff[ ak ] = undefined;
			}
		} );
		return empty ? undefined : Object.freeze( diff );
	}

	// .........................................................................
	static $loadJSFile( src ) {
		return new Promise( resolve => {
			const js = GSUI.$createElement( "script", { src, type: "text/javascript" } );

			js.onload = resolve;
			document.head.append( js );
		} );
	}

	// .........................................................................
	static $findElements( root, graph ) {
		return typeof graph === "string"
			? GSUI.#findElemStr( root, graph )
			: Object.seal( Array.isArray( graph )
				? GSUI.#findElemArr( root, graph )
				: GSUI.#findElemObj( root, graph ) );
	}
	static #findElemArr( root, arr ) {
		return arr.map( sel => GSUI.$findElements( root, sel ) );
	}
	static #findElemObj( root, obj ) {
		if ( obj ) {
			const ent = Object.entries( obj );

			ent.forEach( kv => kv[ 1 ] = GSUI.$findElements( root, kv[ 1 ] ) );
			return Object.fromEntries( ent );
		}
	}
	static #findElemStr( root, sel ) {
		if ( Array.isArray( root ) ) {
			let el;

			Array.prototype.find.call( root, r => el = GSUI.#findElemQuery( r, sel ) );
			return el || null;
		}
		return GSUI.#findElemQuery( root, sel );
	}
	static #findElemQuery( root, sel ) {
		return root.matches( sel )
			? root
			: root.querySelector( sel );
	}

	// .........................................................................
	static $dispatchEvent( el, component, eventName, ...args ) {
		el.dispatchEvent( new CustomEvent( "gsuiEvents", {
			bubbles: true,
			detail: { component, eventName, args },
		} ) );
	}
	static $listenEvents( el, cbs ) {
		el.addEventListener( "gsuiEvents", e => {
			const d = e.detail;
			const cbs2 = cbs[ d.component ] || cbs.default;
			const fn = cbs2 && ( cbs2[ d.eventName ] || cbs2.default );

			if ( fn && fn( d, e.target, e ) !== true ) {
				e.stopPropagation();
				e.stopImmediatePropagation();
			}
		} );
	}

	// .........................................................................
	static #templates = new Map();
	static $setTemplate( tmpId, fn ) {
		GSUI.#templates.set( tmpId, fn );
	}
	static $hasTemplate( tmpId ) {
		return GSUI.#templates.has( tmpId );
	}
	static $getTemplate( tmpId, ...args ) {
		return GSUI.#templates.get( tmpId )( ...args );
	}

	// .........................................................................
	static $createElement( tag, attr, ...children ) {
		return GSUI.#createElement( "http://www.w3.org/1999/xhtml", tag, attr, children );
	}
	static $createElementSVG( tag, attr, ...children ) {
		return GSUI.#createElement( "http://www.w3.org/2000/svg", tag, attr, children );
	}
	static #createElement( ns, tag, attrObj, children ) {
		const el = document.createElementNS( ns, tag );

		GSUI.$setAttribute( el, attrObj );
		el.append( ...children.flat( 1 ).filter( ch => Boolean( ch ) || Number.isFinite( ch ) ) );
		return el;
	}
	static $setAttribute( el, attr, val ) {
		if ( typeof attr === "string" ) {
			GSUI.#setAttribute( el, attr, val );
		} else if ( attr ) {
			Object.entries( attr ).forEach( kv => GSUI.#setAttribute( el, ...kv ) );
		}
	}
	static $setGetAttribute( el, attr, val ) {
		GSUI.#setAttribute( el, attr, val );
		return GSUI.$getAttribute( el, attr );
	}
	static #setAttribute( el, attr, val ) {
		val !== false && val !== null && val !== undefined
			? el.setAttribute( attr, val === true ? "" : val )
			: el.removeAttribute( attr );
	}
	static $hasAttribute( el, attr ) {
		return el.hasAttribute( attr );
	}
	static $getAttribute( el, attr ) {
		return el.getAttribute( attr );
	}
	static $getAttributeNum( el, attr ) {
		const val = el.getAttribute( attr );
		const n = +val;

		if ( Number.isNaN( n ) ) {
			console.error( `GSUI.$getAttributeNum: ${ attr } is NaN (${ val })` );
		}
		return n;
	}

	// .........................................................................
	static $observeSizeOf( el, fn ) {
		if ( GSUI.#resizeMap.has( el ) ) {
			GSUI.#resizeMap.get( el ).push( fn );
		} else {
			GSUI.#resizeMap.set( el, [ fn ] );
		}
		GSUI.#resizeObs.observe( el );
	}
	static $unobserveSizeOf( el, fn ) {
		const fns = GSUI.#resizeMap.get( el );
		const fnInd = fns.indexOf( fn );

		GSUI.#resizeObs.unobserve( el );
		if ( fnInd > -1 ) {
			fns.splice( fnInd, 1 );
			if ( fns.length === 0 ) {
				GSUI.#resizeMap.delete( el );
			}
		}
	}
	static #resizeMap = new Map();
	static #resizeObs = new ResizeObserver( entries => {
		entries.forEach( e => {
			GSUI.#resizeMap.get( e.target )
				.forEach( fn => fn( e.contentRect.width, e.contentRect.height ) );
		} );
	} );

	// .........................................................................
	static $emptyElement( el ) {
		while ( el.lastChild ) {
			el.lastChild.remove();
		}
	}

	// .........................................................................
	static $unselectText() {
		window.getSelection().removeAllRanges();
	}

	// .........................................................................
	static $recallAttributes( el, props ) {
		Object.entries( props ).forEach( ( [ p, val ] ) => {
			el.hasAttribute( p )
				? el.attributeChangedCallback?.( p, null, el.getAttribute( p ) )
				: GSUI.#setAttribute( el, p, val );
		} );
	}
}

document.body.prepend( GSUI.$dragshield, GSUI.$popup );
<commit_msg>gsui.js: $getFilesDataTransfert<commit_after>"use strict";

class GSUI {
	static $noop() {}
	static $popup = document.createElement( "gsui-popup" );
	static $dragshield = document.createElement( "gsui-dragshield" );

	// .........................................................................
	static $clamp( n, min, max ) {
		return (
			min < max
				? Math.max( min, Math.min( n || 0, max ) )
				: Math.max( max, Math.min( n || 0, min ) )
		);
	}

	// .........................................................................
	static $diffObjects( a, b ) {
		let empty = true;
		const diff = Object.entries( b ).reduce( ( diff, [ bk, bv ] ) => {
			const av = a[ bk ];
			const newval = av === bv ? undefined :
				typeof bv !== "object" || bv === null ? bv :
				typeof av !== "object" || av === null
					? Object.freeze( JSON.parse( JSON.stringify( bv ) ) )
					: GSUI.$diffObjects( av, bv );

			if ( newval !== undefined ) {
				empty = false;
				diff[ bk ] = newval;
			}
			return diff;
		}, {} );

		Object.keys( a ).forEach( ak => {
			if ( !( ak in b ) ) {
				empty = false;
				diff[ ak ] = undefined;
			}
		} );
		return empty ? undefined : Object.freeze( diff );
	}

	// .........................................................................
	static $loadJSFile( src ) {
		return new Promise( resolve => {
			const js = GSUI.$createElement( "script", { src, type: "text/javascript" } );

			js.onload = resolve;
			document.head.append( js );
		} );
	}

	// .........................................................................
	static $findElements( root, graph ) {
		return typeof graph === "string"
			? GSUI.#findElemStr( root, graph )
			: Object.seal( Array.isArray( graph )
				? GSUI.#findElemArr( root, graph )
				: GSUI.#findElemObj( root, graph ) );
	}
	static #findElemArr( root, arr ) {
		return arr.map( sel => GSUI.$findElements( root, sel ) );
	}
	static #findElemObj( root, obj ) {
		if ( obj ) {
			const ent = Object.entries( obj );

			ent.forEach( kv => kv[ 1 ] = GSUI.$findElements( root, kv[ 1 ] ) );
			return Object.fromEntries( ent );
		}
	}
	static #findElemStr( root, sel ) {
		if ( Array.isArray( root ) ) {
			let el;

			Array.prototype.find.call( root, r => el = GSUI.#findElemQuery( r, sel ) );
			return el || null;
		}
		return GSUI.#findElemQuery( root, sel );
	}
	static #findElemQuery( root, sel ) {
		return root.matches( sel )
			? root
			: root.querySelector( sel );
	}

	// .........................................................................
	static $dispatchEvent( el, component, eventName, ...args ) {
		el.dispatchEvent( new CustomEvent( "gsuiEvents", {
			bubbles: true,
			detail: { component, eventName, args },
		} ) );
	}
	static $listenEvents( el, cbs ) {
		el.addEventListener( "gsuiEvents", e => {
			const d = e.detail;
			const cbs2 = cbs[ d.component ] || cbs.default;
			const fn = cbs2 && ( cbs2[ d.eventName ] || cbs2.default );

			if ( fn && fn( d, e.target, e ) !== true ) {
				e.stopPropagation();
				e.stopImmediatePropagation();
			}
		} );
	}

	// .........................................................................
	static #templates = new Map();
	static $setTemplate( tmpId, fn ) {
		GSUI.#templates.set( tmpId, fn );
	}
	static $hasTemplate( tmpId ) {
		return GSUI.#templates.has( tmpId );
	}
	static $getTemplate( tmpId, ...args ) {
		return GSUI.#templates.get( tmpId )( ...args );
	}

	// .........................................................................
	static $createElement( tag, attr, ...children ) {
		return GSUI.#createElement( "http://www.w3.org/1999/xhtml", tag, attr, children );
	}
	static $createElementSVG( tag, attr, ...children ) {
		return GSUI.#createElement( "http://www.w3.org/2000/svg", tag, attr, children );
	}
	static #createElement( ns, tag, attrObj, children ) {
		const el = document.createElementNS( ns, tag );

		GSUI.$setAttribute( el, attrObj );
		el.append( ...children.flat( 1 ).filter( ch => Boolean( ch ) || Number.isFinite( ch ) ) );
		return el;
	}
	static $setAttribute( el, attr, val ) {
		if ( typeof attr === "string" ) {
			GSUI.#setAttribute( el, attr, val );
		} else if ( attr ) {
			Object.entries( attr ).forEach( kv => GSUI.#setAttribute( el, ...kv ) );
		}
	}
	static $setGetAttribute( el, attr, val ) {
		GSUI.#setAttribute( el, attr, val );
		return GSUI.$getAttribute( el, attr );
	}
	static #setAttribute( el, attr, val ) {
		val !== false && val !== null && val !== undefined
			? el.setAttribute( attr, val === true ? "" : val )
			: el.removeAttribute( attr );
	}
	static $hasAttribute( el, attr ) {
		return el.hasAttribute( attr );
	}
	static $getAttribute( el, attr ) {
		return el.getAttribute( attr );
	}
	static $getAttributeNum( el, attr ) {
		const val = el.getAttribute( attr );
		const n = +val;

		if ( Number.isNaN( n ) ) {
			console.error( `GSUI.$getAttributeNum: ${ attr } is NaN (${ val })` );
		}
		return n;
	}

	// .........................................................................
	static $observeSizeOf( el, fn ) {
		if ( GSUI.#resizeMap.has( el ) ) {
			GSUI.#resizeMap.get( el ).push( fn );
		} else {
			GSUI.#resizeMap.set( el, [ fn ] );
		}
		GSUI.#resizeObs.observe( el );
	}
	static $unobserveSizeOf( el, fn ) {
		const fns = GSUI.#resizeMap.get( el );
		const fnInd = fns.indexOf( fn );

		GSUI.#resizeObs.unobserve( el );
		if ( fnInd > -1 ) {
			fns.splice( fnInd, 1 );
			if ( fns.length === 0 ) {
				GSUI.#resizeMap.delete( el );
			}
		}
	}
	static #resizeMap = new Map();
	static #resizeObs = new ResizeObserver( entries => {
		entries.forEach( e => {
			GSUI.#resizeMap.get( e.target )
				.forEach( fn => fn( e.contentRect.width, e.contentRect.height ) );
		} );
	} );

	// .........................................................................
	static $emptyElement( el ) {
		while ( el.lastChild ) {
			el.lastChild.remove();
		}
	}

	// .........................................................................
	static $unselectText() {
		window.getSelection().removeAllRanges();
	}

	// .........................................................................
	static $recallAttributes( el, props ) {
		Object.entries( props ).forEach( ( [ p, val ] ) => {
			el.hasAttribute( p )
				? el.attributeChangedCallback?.( p, null, el.getAttribute( p ) )
				: GSUI.#setAttribute( el, p, val );
		} );
	}

	// .........................................................................
	static $getFilesDataTransfert( dataTransferItems ) {
		const files = [];

		return new Promise( res => {
			const proms = [];

			for ( let it of dataTransferItems ) {
				proms.push( GSUI.#getFilesDataTransfertRec( files, it.webkitGetAsEntry() ) );
			}
			Promise.all( proms ).then( () => res( files ) );
		} );
	}
	static #getFilesDataTransfertRec( files, item, path = "" ) {
		return new Promise( res => {
			if ( item.isFile ) {
				item.file( f => {
					f.filepath = path + f.name;
					files.push( f );
					res( f );
				} );
			} else if ( item.isDirectory ) {
				const dirReader = item.createReader();

				dirReader.readEntries( entries => {
					const proms = [];

					for ( let ent of entries ) {
						proms.push( GSUI.#getFilesDataTransfertRec( files, ent, path + item.name + "/" ) );
					}
					res( Promise.all( proms ) );
				} );
			}
		} );
	}
}

document.body.prepend( GSUI.$dragshield, GSUI.$popup );
<|endoftext|>


<commit_before>/**
 * @fileoverview This file provides a "infobar" directive. This directive is
 * used to insert an Info Bar into the HTML page.
 * Example:
 *
 * <app-infobar app-infobar-map="::mainCtrl.map"map></app-infobar>
 *
 * Note the use of the one-time binding operator (::) in the map expression.
 * One-time binding is used because we know the map is not going to change
 * during the lifetime of the application.
 *
 */
goog.provide('app.infobarDirective');
goog.require('app');
goog.require('ol.control.MousePosition');

/**
 * @return {angular.Directive} The Directive Object Definition.
 * @param {string} appInfobarTemplateUrl
 * @ngInject
 */
app.infobarDirective = function(appInfobarTemplateUrl) {
  return {
    restrict: 'E',
    scope: {
      'map': '=appInfobarMap'
    },
    controller: 'AppInfobarController',
    controllerAs: 'ctrl',
    bindToController: true,
    templateUrl: appInfobarTemplateUrl
  };
};

app.module.directive('appInfobar', app.infobarDirective);

/**
 * @ngInject
 * @constructor
 */
app.InfobarDirectiveController = function() {
    this['infobarOpen'] = false;
};

app.InfobarDirectiveController.prototype.infobarSwitch = function(){
    if (this['infobarOpen']){
        this['infobarOpen'] = false;
    } else {
        this['infobarOpen'] = true;
    };
}

app.module.controller('AppInfobarController',
    app.InfobarDirectiveController);
<commit_msg>infobar google closure annotations<commit_after>/**
 * @fileoverview This file provides a "infobar" directive. This directive is
 * used to insert an Info Bar into the HTML page.
 * Example:
 *
 * <app-infobar app-infobar-map="::mainCtrl.map"map></app-infobar>
 *
 * Note the use of the one-time binding operator (::) in the map expression.
 * One-time binding is used because we know the map is not going to change
 * during the lifetime of the application.
 *
 */
goog.provide('app.infobarDirective');
goog.require('app');
goog.require('ol.control.MousePosition');

/**
 * @return {angular.Directive} The Directive Object Definition.
 * @param {string} appInfobarTemplateUrl
 * @ngInject
 */
app.infobarDirective = function(appInfobarTemplateUrl) {
  return {
    restrict: 'E',
    scope: {
      'map': '=appInfobarMap'
    },
    controller: 'AppInfobarController',
    controllerAs: 'ctrl',
    bindToController: true,
    templateUrl: appInfobarTemplateUrl
  };
};

app.module.directive('appInfobar', app.infobarDirective);

/**
 * @ngInject
 * @constructor
 * @export
 */
app.InfobarDirectiveController = function() {
  /**
     * @type {boolean}
     */
  this['infobarOpen'] = false;
};

/**
 * @export
 */
app.InfobarDirectiveController.prototype.infobarSwitch = function() {
  if (this['infobarOpen']) {
    this['infobarOpen'] = false;
  } else {
    this['infobarOpen'] = true;
  }
};

app.module.controller('AppInfobarController',
    app.InfobarDirectiveController);
<|endoftext|>


<commit_before>import Controller from '@ember/controller';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object';
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';

const CURRENCIES = currencies.map((currency) => {
    return {
        value: currency.isoCode.toLowerCase(),
        label: `${currency.isoCode} - ${currency.name}`,
        isoCode: currency.isoCode
    };
});

export default class MembersAccessController extends Controller {
    @service config;
    @service feature;
    @service membersUtils;
    @service settings;
    @service store;
    @service session;

    @tracked showLeavePortalModal = false;
    @tracked showLeaveRouteModal = false;
    @tracked showPortalSettings = false;
    @tracked showStripeConnect = false;
    @tracked showProductModal = false;

    @tracked product = null;
    @tracked products = null;
    @tracked productModel = null;
    @tracked paidSignupRedirect;
    @tracked freeSignupRedirect;
    @tracked welcomePageURL;
    @tracked stripeMonthlyAmount = 5;
    @tracked stripeYearlyAmount = 50;
    @tracked currency = 'usd';
    @tracked stripePlanError = '';

    @tracked portalPreviewUrl = '';

    portalPreviewGuid = Date.now().valueOf();

    queryParams = ['showPortalSettings'];

    get freeProduct() {
        return this.products?.find(product => product.type === 'free');
    }

    get paidProducts() {
        return this.products?.filter(product => product.type === 'paid');
    }

    get allCurrencies() {
        return getCurrencyOptions();
    }

    get siteUrl() {
        return this.config.get('blogUrl');
    }

    get selectedCurrency() {
        return CURRENCIES.findBy('value', this.currency);
    }

    get isConnectDisallowed() {
        const siteUrl = this.config.get('blogUrl');
        return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
    }

    get hasChangedPrices() {
        if (this.product) {
            const monthlyPrice = this.product.get('monthlyPrice');
            const yearlyPrice = this.product.get('yearlyPrice');

            if (monthlyPrice?.amount && parseFloat(this.stripeMonthlyAmount) !== (monthlyPrice.amount / 100)) {
                return true;
            }
            if (yearlyPrice?.amount && parseFloat(this.stripeYearlyAmount) !== (yearlyPrice.amount / 100)) {
                return true;
            }
        }

        return false;
    }

    @action
    setup() {
        this.fetchProducts.perform();
        this.updatePortalPreview();
    }

    leaveRoute(transition) {
        if (this.settings.get('hasDirtyAttributes') || this.hasChangedPrices) {
            transition.abort();
            this.leaveSettingsTransition = transition;
            this.showLeaveRouteModal = true;
        }
    }

    @action
    async confirmLeave() {
        this.settings.rollbackAttributes();
        this.resetPrices();
        this.leaveSettingsTransition.retry();
    }

    @action
    cancelLeave() {
        this.showLeaveRouteModal = false;
        this.leaveSettingsTransition = null;
    }

    @action
    async membersSubscriptionAccessChanged() {
        const oldValue = this.settings.changedAttributes().membersSignupAccess?.[0];

        if (oldValue === 'none') {
            // when saved value is 'none' the server won't inject the portal script
            // to work around that and show the expected portal preview we save and
            // force a refresh
            await this.switchFromNoneTask.perform();
        } else {
            this.updatePortalPreview();
        }
    }

    @action
    setStripePlansCurrency(event) {
        const newCurrency = event.value;
        this.currency = newCurrency;
    }

    @action
    setPaidSignupRedirect(url) {
        this.paidSignupRedirect = url;
    }

    @action
    setFreeSignupRedirect(url) {
        this.freeSignupRedirect = url;
    }

    @action
    setWelcomePageURL(url) {
        this.welcomePageURL = url;
    }

    @action
    validatePaidSignupRedirect() {
        return this._validateSignupRedirect(this.paidSignupRedirect, 'membersPaidSignupRedirect');
    }

    @action
    validateFreeSignupRedirect() {
        return this._validateSignupRedirect(this.freeSignupRedirect, 'membersFreeSignupRedirect');
    }

    @action
    validateWelcomePageURL() {
        const siteUrl = this.siteUrl;

        if (this.welcomePageURL === undefined) {
            // Not initialised
            return;
        }

        if (this.welcomePageURL.href.startsWith(siteUrl)) {
            const path = this.welcomePageURL.href.replace(siteUrl, '');
            this.freeProduct.welcomePageURL = path;
        } else {
            this.freeProduct.welcomePageURL = this.welcomePageURL.href;
        }
    }

    @action
    validateStripePlans({updatePortalPreview = true} = {}) {
        this.stripePlanError = undefined;

        try {
            const yearlyAmount = this.stripeYearlyAmount;
            const monthlyAmount = this.stripeMonthlyAmount;
            const symbol = getSymbol(this.currency);
            if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
                throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
            }

            if (updatePortalPreview) {
                this.updatePortalPreview();
            }
        } catch (err) {
            this.stripePlanError = err.message;
        }
    }

    @action
    openStripeConnect() {
        this.stripeEnabledOnOpen = this.membersUtils.isStripeEnabled;
        this.showStripeConnect = true;
    }

    @action
    async closeStripeConnect() {
        if (this.stripeEnabledOnOpen !== this.membersUtils.isStripeEnabled) {
            await this.saveSettingsTask.perform({forceRefresh: true});
        }
        this.showStripeConnect = false;
    }

    @action
    async openEditProduct(product) {
        this.productModel = product;
        this.showProductModal = true;
    }

    @action
    async openNewProduct() {
        this.productModel = this.store.createRecord('product');
        this.showProductModal = true;
    }

    @action
    closeProductModal() {
        this.showProductModal = false;
    }

    @action
    openPortalSettings() {
        this.saveSettingsTask.perform();
        this.showPortalSettings = true;
    }

    @action
    closePortalSettings() {
        const changedAttributes = this.settings.changedAttributes();
        if (changedAttributes && Object.keys(changedAttributes).length > 0) {
            this.showLeavePortalModal = true;
        } else {
            this.showPortalSettings = false;
            this.updatePortalPreview();
        }
    }

    @action
    async confirmClosePortalSettings() {
        this.settings.rollbackAttributes();
        this.showPortalSettings = false;
        this.showLeavePortalModal = false;
        this.updatePortalPreview();
    }

    @action
    cancelClosePortalSettings() {
        this.showLeavePortalModal = false;
    }

    @action
    updatePortalPreview({forceRefresh} = {forceRefresh: false}) {
        // TODO: can these be worked out from settings in membersUtils?
        const monthlyPrice = Math.round(this.stripeMonthlyAmount * 100);
        const yearlyPrice = Math.round(this.stripeYearlyAmount * 100);
        let portalPlans = this.settings.get('portalPlans') || [];

        let isMonthlyChecked = portalPlans.includes('monthly');
        let isYearlyChecked = portalPlans.includes('yearly');

        const newUrl = new URL(this.membersUtils.getPortalPreviewUrl({
            button: false,
            monthlyPrice,
            yearlyPrice,
            currency: this.currency,
            isMonthlyChecked,
            isYearlyChecked,
            portalPlans: null
        }));

        if (forceRefresh) {
            this.portalPreviewGuid = Date.now().valueOf();
        }
        newUrl.searchParams.set('v', this.portalPreviewGuid);

        this.portalPreviewUrl = newUrl;
    }

    @action
    portalPreviewInserted(iframe) {
        this.portalPreviewIframe = iframe;

        if (!this.portalMessageListener) {
            this.portalMessageListener = (event) => {
                // don't resize membership portal preview when events fire in customize portal modal
                if (this.showPortalSettings) {
                    return;
                }

                const resizeEvents = ['portal-ready', 'portal-preview-updated'];
                if (resizeEvents.includes(event.data.type) && event.data.payload?.height && this.portalPreviewIframe?.parentNode) {
                    this.portalPreviewIframe.parentNode.style.height = `${event.data.payload.height}px`;
                }
            };

            window.addEventListener('message', this.portalMessageListener, true);
        }
    }

    @action
    portalPreviewDestroyed() {
        this.portalPreviewIframe = null;

        if (this.portalMessageListener) {
            window.removeEventListener('message', this.portalMessageListener);
        }
    }

    @action
    confirmProductSave() {
        this.updatePortalPreview({forceRefresh: true});
        return this.fetchProducts.perform();
    }

    @task
    *switchFromNoneTask() {
        return yield this.saveSettingsTask.perform({forceRefresh: true});
    }

    setupPortalProduct(product) {
        if (product) {
            const monthlyPrice = product.get('monthlyPrice');
            const yearlyPrice = product.get('yearlyPrice');
            if (monthlyPrice && monthlyPrice.amount) {
                this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
                this.currency = monthlyPrice.currency;
            }
            if (yearlyPrice && yearlyPrice.amount) {
                this.stripeYearlyAmount = (yearlyPrice.amount / 100);
            }
            this.updatePortalPreview();
        }
    }

    @task({drop: true})
    *fetchProducts() {
        this.products = yield this.store.query('product', {
            include: 'monthly_price,yearly_price,benefits'
        });
        this.product = this.paidProducts.firstObject;
        this.setupPortalProduct(this.product);
    }

    @task({drop: true})
    *saveSettingsTask(options) {
        if (!this.feature.get('multipleProducts')) {
            yield this.validateStripePlans({updatePortalPreview: false});

            if (this.stripePlanError) {
                return;
            }

            if (this.settings.get('errors').length !== 0) {
                return;
            }

            yield this.saveProduct();
            const result = yield this.settings.save();

            this.updatePortalPreview(options);

            return result;
        } else {
            if (this.settings.get('errors').length !== 0) {
                return;
            }
            // When no filer is selected in `Specific tier(s)` option
            if (!this.settings.get('defaultContentVisibility')) {
                return;
            }
            const result = yield this.settings.save();
            yield this.freeProduct.save();
            this.updatePortalPreview(options);
            return result;
        }
    }

    async saveProduct() {
        const isStripeConnected = this.settings.get('stripeConnectAccountId');
        if (this.product && isStripeConnected) {
            const monthlyAmount = this.stripeMonthlyAmount * 100;
            const yearlyAmount = this.stripeYearlyAmount * 100;

            this.product.set('monthlyPrice', {
                nickname: 'Monthly',
                amount: monthlyAmount,
                active: true,
                currency: this.currency,
                interval: 'month',
                type: 'recurring'
            });
            this.product.set('yearlyPrice', {
                nickname: 'Yearly',
                amount: yearlyAmount,
                active: true,
                currency: this.currency,
                interval: 'year',
                type: 'recurring'
            });

            const savedProduct = await this.product.save();
            return savedProduct;
        }
    }

    resetPrices() {
        const monthlyPrice = this.product.get('monthlyPrice');
        const yearlyPrice = this.product.get('yearlyPrice');

        this.stripeMonthlyAmount = monthlyPrice ? (monthlyPrice.amount / 100) : 5;
        this.stripeYearlyAmount = yearlyPrice ? (yearlyPrice.amount / 100) : 50;
    }

    reset() {
        this.showLeaveRouteModal = false;
        this.showLeavePortalModal = false;
        this.showPortalSettings = false;
    }

    _validateSignupRedirect(url, type) {
        const siteUrl = this.config.get('blogUrl');
        let errMessage = `Please enter a valid URL`;
        this.settings.get('errors').remove(type);
        this.settings.get('hasValidated').removeObject(type);

        if (url === null) {
            this.settings.get('errors').add(type, errMessage);
            this.settings.get('hasValidated').pushObject(type);
            return false;
        }

        if (url === undefined) {
            // Not initialised
            return;
        }

        if (url.href.startsWith(siteUrl)) {
            const path = url.href.replace(siteUrl, '');
            this.settings.set(type, path);
        } else {
            this.settings.set(type, url.href);
        }
    }
}
<commit_msg>Revert "Rounded monthly/yearly Price"<commit_after>import Controller from '@ember/controller';
import envConfig from 'ghost-admin/config/environment';
import {action} from '@ember/object';
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';

const CURRENCIES = currencies.map((currency) => {
    return {
        value: currency.isoCode.toLowerCase(),
        label: `${currency.isoCode} - ${currency.name}`,
        isoCode: currency.isoCode
    };
});

export default class MembersAccessController extends Controller {
    @service config;
    @service feature;
    @service membersUtils;
    @service settings;
    @service store;
    @service session;

    @tracked showLeavePortalModal = false;
    @tracked showLeaveRouteModal = false;
    @tracked showPortalSettings = false;
    @tracked showStripeConnect = false;
    @tracked showProductModal = false;

    @tracked product = null;
    @tracked products = null;
    @tracked productModel = null;
    @tracked paidSignupRedirect;
    @tracked freeSignupRedirect;
    @tracked welcomePageURL;
    @tracked stripeMonthlyAmount = 5;
    @tracked stripeYearlyAmount = 50;
    @tracked currency = 'usd';
    @tracked stripePlanError = '';

    @tracked portalPreviewUrl = '';

    portalPreviewGuid = Date.now().valueOf();

    queryParams = ['showPortalSettings'];

    get freeProduct() {
        return this.products?.find(product => product.type === 'free');
    }

    get paidProducts() {
        return this.products?.filter(product => product.type === 'paid');
    }

    get allCurrencies() {
        return getCurrencyOptions();
    }

    get siteUrl() {
        return this.config.get('blogUrl');
    }

    get selectedCurrency() {
        return CURRENCIES.findBy('value', this.currency);
    }

    get isConnectDisallowed() {
        const siteUrl = this.config.get('blogUrl');
        return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
    }

    get hasChangedPrices() {
        if (this.product) {
            const monthlyPrice = this.product.get('monthlyPrice');
            const yearlyPrice = this.product.get('yearlyPrice');

            if (monthlyPrice?.amount && parseFloat(this.stripeMonthlyAmount) !== (monthlyPrice.amount / 100)) {
                return true;
            }
            if (yearlyPrice?.amount && parseFloat(this.stripeYearlyAmount) !== (yearlyPrice.amount / 100)) {
                return true;
            }
        }

        return false;
    }

    @action
    setup() {
        this.fetchProducts.perform();
        this.updatePortalPreview();
    }

    leaveRoute(transition) {
        if (this.settings.get('hasDirtyAttributes') || this.hasChangedPrices) {
            transition.abort();
            this.leaveSettingsTransition = transition;
            this.showLeaveRouteModal = true;
        }
    }

    @action
    async confirmLeave() {
        this.settings.rollbackAttributes();
        this.resetPrices();
        this.leaveSettingsTransition.retry();
    }

    @action
    cancelLeave() {
        this.showLeaveRouteModal = false;
        this.leaveSettingsTransition = null;
    }

    @action
    async membersSubscriptionAccessChanged() {
        const oldValue = this.settings.changedAttributes().membersSignupAccess?.[0];

        if (oldValue === 'none') {
            // when saved value is 'none' the server won't inject the portal script
            // to work around that and show the expected portal preview we save and
            // force a refresh
            await this.switchFromNoneTask.perform();
        } else {
            this.updatePortalPreview();
        }
    }

    @action
    setStripePlansCurrency(event) {
        const newCurrency = event.value;
        this.currency = newCurrency;
    }

    @action
    setPaidSignupRedirect(url) {
        this.paidSignupRedirect = url;
    }

    @action
    setFreeSignupRedirect(url) {
        this.freeSignupRedirect = url;
    }

    @action
    setWelcomePageURL(url) {
        this.welcomePageURL = url;
    }

    @action
    validatePaidSignupRedirect() {
        return this._validateSignupRedirect(this.paidSignupRedirect, 'membersPaidSignupRedirect');
    }

    @action
    validateFreeSignupRedirect() {
        return this._validateSignupRedirect(this.freeSignupRedirect, 'membersFreeSignupRedirect');
    }

    @action
    validateWelcomePageURL() {
        const siteUrl = this.siteUrl;

        if (this.welcomePageURL === undefined) {
            // Not initialised
            return;
        }

        if (this.welcomePageURL.href.startsWith(siteUrl)) {
            const path = this.welcomePageURL.href.replace(siteUrl, '');
            this.freeProduct.welcomePageURL = path;
        } else {
            this.freeProduct.welcomePageURL = this.welcomePageURL.href;
        }
    }

    @action
    validateStripePlans({updatePortalPreview = true} = {}) {
        this.stripePlanError = undefined;

        try {
            const yearlyAmount = this.stripeYearlyAmount;
            const monthlyAmount = this.stripeMonthlyAmount;
            const symbol = getSymbol(this.currency);
            if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
                throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
            }

            if (updatePortalPreview) {
                this.updatePortalPreview();
            }
        } catch (err) {
            this.stripePlanError = err.message;
        }
    }

    @action
    openStripeConnect() {
        this.stripeEnabledOnOpen = this.membersUtils.isStripeEnabled;
        this.showStripeConnect = true;
    }

    @action
    async closeStripeConnect() {
        if (this.stripeEnabledOnOpen !== this.membersUtils.isStripeEnabled) {
            await this.saveSettingsTask.perform({forceRefresh: true});
        }
        this.showStripeConnect = false;
    }

    @action
    async openEditProduct(product) {
        this.productModel = product;
        this.showProductModal = true;
    }

    @action
    async openNewProduct() {
        this.productModel = this.store.createRecord('product');
        this.showProductModal = true;
    }

    @action
    closeProductModal() {
        this.showProductModal = false;
    }

    @action
    openPortalSettings() {
        this.saveSettingsTask.perform();
        this.showPortalSettings = true;
    }

    @action
    closePortalSettings() {
        const changedAttributes = this.settings.changedAttributes();
        if (changedAttributes && Object.keys(changedAttributes).length > 0) {
            this.showLeavePortalModal = true;
        } else {
            this.showPortalSettings = false;
            this.updatePortalPreview();
        }
    }

    @action
    async confirmClosePortalSettings() {
        this.settings.rollbackAttributes();
        this.showPortalSettings = false;
        this.showLeavePortalModal = false;
        this.updatePortalPreview();
    }

    @action
    cancelClosePortalSettings() {
        this.showLeavePortalModal = false;
    }

    @action
    updatePortalPreview({forceRefresh} = {forceRefresh: false}) {
        // TODO: can these be worked out from settings in membersUtils?
        const monthlyPrice = this.stripeMonthlyAmount * 100;
        const yearlyPrice = this.stripeYearlyAmount * 100;
        let portalPlans = this.settings.get('portalPlans') || [];

        let isMonthlyChecked = portalPlans.includes('monthly');
        let isYearlyChecked = portalPlans.includes('yearly');

        const newUrl = new URL(this.membersUtils.getPortalPreviewUrl({
            button: false,
            monthlyPrice,
            yearlyPrice,
            currency: this.currency,
            isMonthlyChecked,
            isYearlyChecked,
            portalPlans: null
        }));

        if (forceRefresh) {
            this.portalPreviewGuid = Date.now().valueOf();
        }
        newUrl.searchParams.set('v', this.portalPreviewGuid);

        this.portalPreviewUrl = newUrl;
    }

    @action
    portalPreviewInserted(iframe) {
        this.portalPreviewIframe = iframe;

        if (!this.portalMessageListener) {
            this.portalMessageListener = (event) => {
                // don't resize membership portal preview when events fire in customize portal modal
                if (this.showPortalSettings) {
                    return;
                }

                const resizeEvents = ['portal-ready', 'portal-preview-updated'];
                if (resizeEvents.includes(event.data.type) && event.data.payload?.height && this.portalPreviewIframe?.parentNode) {
                    this.portalPreviewIframe.parentNode.style.height = `${event.data.payload.height}px`;
                }
            };

            window.addEventListener('message', this.portalMessageListener, true);
        }
    }

    @action
    portalPreviewDestroyed() {
        this.portalPreviewIframe = null;

        if (this.portalMessageListener) {
            window.removeEventListener('message', this.portalMessageListener);
        }
    }

    @action
    confirmProductSave() {
        this.updatePortalPreview({forceRefresh: true});
        return this.fetchProducts.perform();
    }

    @task
    *switchFromNoneTask() {
        return yield this.saveSettingsTask.perform({forceRefresh: true});
    }

    setupPortalProduct(product) {
        if (product) {
            const monthlyPrice = product.get('monthlyPrice');
            const yearlyPrice = product.get('yearlyPrice');
            if (monthlyPrice && monthlyPrice.amount) {
                this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
                this.currency = monthlyPrice.currency;
            }
            if (yearlyPrice && yearlyPrice.amount) {
                this.stripeYearlyAmount = (yearlyPrice.amount / 100);
            }
            this.updatePortalPreview();
        }
    }

    @task({drop: true})
    *fetchProducts() {
        this.products = yield this.store.query('product', {
            include: 'monthly_price,yearly_price,benefits'
        });
        this.product = this.paidProducts.firstObject;
        this.setupPortalProduct(this.product);
    }

    @task({drop: true})
    *saveSettingsTask(options) {
        if (!this.feature.get('multipleProducts')) {
            yield this.validateStripePlans({updatePortalPreview: false});

            if (this.stripePlanError) {
                return;
            }

            if (this.settings.get('errors').length !== 0) {
                return;
            }

            yield this.saveProduct();
            const result = yield this.settings.save();

            this.updatePortalPreview(options);

            return result;
        } else {
            if (this.settings.get('errors').length !== 0) {
                return;
            }
            // When no filer is selected in `Specific tier(s)` option
            if (!this.settings.get('defaultContentVisibility')) {
                return;
            }
            const result = yield this.settings.save();
            yield this.freeProduct.save();
            this.updatePortalPreview(options);
            return result;
        }
    }

    async saveProduct() {
        const isStripeConnected = this.settings.get('stripeConnectAccountId');
        if (this.product && isStripeConnected) {
            const monthlyAmount = this.stripeMonthlyAmount * 100;
            const yearlyAmount = this.stripeYearlyAmount * 100;

            this.product.set('monthlyPrice', {
                nickname: 'Monthly',
                amount: monthlyAmount,
                active: true,
                currency: this.currency,
                interval: 'month',
                type: 'recurring'
            });
            this.product.set('yearlyPrice', {
                nickname: 'Yearly',
                amount: yearlyAmount,
                active: true,
                currency: this.currency,
                interval: 'year',
                type: 'recurring'
            });

            const savedProduct = await this.product.save();
            return savedProduct;
        }
    }

    resetPrices() {
        const monthlyPrice = this.product.get('monthlyPrice');
        const yearlyPrice = this.product.get('yearlyPrice');

        this.stripeMonthlyAmount = monthlyPrice ? (monthlyPrice.amount / 100) : 5;
        this.stripeYearlyAmount = yearlyPrice ? (yearlyPrice.amount / 100) : 50;
    }

    reset() {
        this.showLeaveRouteModal = false;
        this.showLeavePortalModal = false;
        this.showPortalSettings = false;
    }

    _validateSignupRedirect(url, type) {
        const siteUrl = this.config.get('blogUrl');
        let errMessage = `Please enter a valid URL`;
        this.settings.get('errors').remove(type);
        this.settings.get('hasValidated').removeObject(type);

        if (url === null) {
            this.settings.get('errors').add(type, errMessage);
            this.settings.get('hasValidated').pushObject(type);
            return false;
        }

        if (url === undefined) {
            // Not initialised
            return;
        }

        if (url.href.startsWith(siteUrl)) {
            const path = url.href.replace(siteUrl, '');
            this.settings.set(type, path);
        } else {
            this.settings.set(type, url.href);
        }
    }
}
<|endoftext|>