<commit_before>import Route from '@ember/routing/route';
import { tracked } from '@glimmer/tracking';
import { later, cancel } from '@ember/runloop';
import { Promise } from 'rsvp';
import { set as emSet } from '@ember/object';
import { inject as service } from '@ember/service';
import { debug } from '@ember/debug';
import $ from "jquery";

class Content {
    @tracked user = null;
    @tracked page = null;
    @tracked ping = null;
    @tracked refresh = null;
    @tracked structure = null;
    @tracked rtoken = null;
    @tracked status = null;
    @tracked popup = null;
    @tracked tabs = [];
    @tracked navEntries = [];
    @tracked error = null;
    @tracked showLoadingBanner = false;
}

export default class OpenXpkiRoute extends Route {
    @service('oxi-config') oxiConfig;
    @service('oxi-locale') oxiLocale;
    @service('intl') intl;

    // Reserved Ember property "queryParams"
    // https://api.emberjs.com/ember/3.17/classes/Route/properties/queryParams?anchor=queryParams
    queryParams = {
        // refreshModel==true causes an "in-place" transition, so the model
        // hooks for this route (and any child routes) will re-fire
        startat:  { refreshModel: true },
        limit:    { refreshModel: true },
        force:    { refreshModel: true },
    };
    needReboot = ["login", "logout", "login!logout", "welcome"];

    @tracked content = new Content();
    quietRequest = false;

    // Reserved Ember function
    beforeModel(transition) {
        let queryParams = transition.to.queryParams;
        let modelId = transition.to.params.model_id;
        debug("openxpki/route - beforeModel: model_id = " + modelId);

        // "force" is only evaluated above using "refreshModel: true"
        if (queryParams.force) {
            delete queryParams.force;
        }

        let structureIfNeeded; // chain of ajax calls via Promises

        /*
         * load base page structure first first time or for special pages ("needReboot")
         */
        if (!this.content.navEntries.length || this.needReboot.indexOf(modelId) >= 0) {
            // don't send request yet, only create a lambda via arrow function expression
            structureIfNeeded = () => {
                return this.sendAjax({
                    page: "bootstrap!structure",
                    baseurl: window.location.pathname,
                });
            };
        }
        else {
            structureIfNeeded = () => { Promise.resolve() };
        }

        /*
         * load requested page part
         */
        let request = {
            page: modelId
        };
        if (queryParams.limit) { request.limit = queryParams.limit }
        if (queryParams.startat) { request.startat = queryParams.startat }

        // load as top content if 'modelId' is part of navigation or in 'needReboot' list
        let flatList = this.content.navEntries.reduce((p, n) => p.concat(n, n.entries || []), []);
        if (flatList.findBy("key", modelId) || this.needReboot.indexOf(modelId) >= 0) {
            request.target = "top";
        }
        return this.oxiConfig.ready // localconfig.js might change rootURL, so first thing is to query it
            .then( () => structureIfNeeded() )
            .then( () => this.sendAjax(request) );
    }

    // Reserved Ember function
    model(params/*, transition*/) {
        this.content.page = params.model_id; this.updateNavEntryActiveState(this.content);
        return this.content;
    }

    doPing(cfg) {
        this.content.ping = later(this, () => {
            $.ajax({ url: cfg.href });
            return this.doPing(cfg);
        }, cfg.timeout);
    }

    setLoadingState(isLoading) {
        // don't show optical hints if quietRequest
        if (this.quietRequest) return;

        // note that we cannot use the Ember "loading" event as this would only
        // trigger on route changes, but not if we do sendAjax()
        if (isLoading) {
            // remove focus from button to prevent user from doing another
            // submit by hitting enter
            document.activeElement.blur();
        }
        this.content.showLoadingBanner = isLoading;
    }

    // sends an AJAX request without showing "loading" banner or dimming page
    sendAjaxQuiet(request) {
        this.quietRequest = true;
        return this.sendAjax(request);
    }

    sendAjax(request) {
        this.quietRequest = false;
        this.setLoadingState(true);
        debug("openxpki/route - sendAjax: " + ['page','action'].map(p=>request[p]?`${p} = ${request[p]}`:null).filter(e=>e!==null).join(", "));
        // assemble request parameters
        let req = {
            data: {
                ...request,
                "_": new Date().getTime(),
            },
            dataType: "json",
            type: request.action ? "POST" : "GET",
            url: this.oxiConfig.backendUrl,
        };
        if (req.type === "POST") req.data._rtoken = this.content.rtoken;

        if (this.content.refresh) {
            cancel(this.content.refresh);
            this.content.refresh = null;
        }

        /* eslint-disable-next-line no-unused-vars */
        return new Promise((resolve, reject) => {
            $.ajax(req).then(
                // SUCCESS
                doc => {
                    // work with a copy of this.content
                    this.content.status = doc.status;
                    this.content.popup = null;

                    if (doc.ping) {
                        debug("openxpki/route - sendAjax response: \"ping\" " + doc.ping);
                        if (this.content.ping) { cancel(this.content.ping) }
                        this.doPing(doc.ping);
                    }
                    if (doc.refresh) {
                        debug("openxpki/route - sendAjax response: \"refresh\" " + doc.refresh.href + ", " + doc.refresh.timeout);
                        this.content.refresh = later(this, function() {
                            this.sendAjax({ page: doc.refresh.href });
                        }, doc.refresh.timeout);
                    }
                    if (doc.goto) {
                        debug("openxpki/route - sendAjax response: \"goto\" " + doc.goto);
                        if (doc.target === '_blank' || /^(http|\/)/.test(doc.goto)) {
                            window.location.href = doc.goto;
                        }
                        else {
                            this.transitionTo("openxpki", doc.goto);
                        }
                    }
                    else if (doc.structure) {
                        debug("openxpki/route - sendAjax response: \"structure\"");
                        this.content.navEntries = doc.structure; this.updateNavEntryActiveState();
                        this.content.user = doc.user;
                        this.content.rtoken = doc.rtoken;

                        // set locale
                        if (doc.language) this.oxiLocale.locale = doc.language;
                    }
                    else {
                        if (doc.page && doc.main) {
                            debug("openxpki/route - sendAjax response: \"page\" and \"main\"");
                            this._setPageContent(req.data.target, doc.page, doc.main, doc.right);
                        }
                    }
                    this.setLoadingState(false);
                    return resolve(doc);
                },
                // FAILURE
                () => {
                    this.setLoadingState(false);
                    this.content.error = {
                        message: this.intl.t('error_popup.message')
                    };
                    return resolve({});
                }
            );
        });
    }

    _setPageContent(target = 'self', page, main, right) {
        let newTab = {
            active: true,
            page: page,
            main: main,
            right: right
        };

        // Mark the first form on screen: only the first one is allowed to focus
        // its first input field.
        let isFirst = true;
        for (const section of [...(newTab.main||[]), ...(newTab.right||[])]) {
            if (section.type === "form") {
                section.content.isFirstForm = isFirst;
                if (isFirst) isFirst = false;
            }
        }

        // Pseudo-target "self" is transformed so new content will be shown in the
        // currently active place: a modal popup, an active tab or on top (i.e. single hidden tab)
        if (target === 'self') {
            if (this.content.popup) { target = 'popup' }
            else if (this.content.tabs.length > 1) { target = 'active' }
            else { target = 'top' }
        }
        if (target === 'modal') target = 'popup'; // FIXME legacy naming

        // Popup
        if (target === "popup") {
            this.content.popup = newTab;
        }
        // New tab
        else if (target === "tab") {
            let tabs = this.content.tabs;
            tabs.setEach("active", false);
            tabs.pushObject(newTab);
        }
        // Current tab
        else if (target === "active") {
            let tabs = this.content.tabs;
            let index = tabs.indexOf(tabs.findBy("active"));
            tabs.replace(index, 1, [newTab]); // top
        }
        // Set as only tab
        else {
            this.content.tabs = [newTab];
        }
    }

    updateNavEntryActiveState() {
        let page = this.content.page;
        for (const entry of this.content.navEntries) {
            emSet(entry, "active", (entry.key === page));
            if (entry.entries) {
                entry.entries.setEach("active", false);
                let subEntry = entry.entries.findBy("key", page);
                if (subEntry) {
                    emSet(subEntry, "active", true);
                    emSet(entry, "active", true);
                }
            }
        }
        this.content.navEntries = this.content.navEntries; // trigger updates
    }
}<commit_msg>UI: fix regression bug introduced in 5b1f12e (target handling)<commit_after>import Route from '@ember/routing/route';
import { tracked } from '@glimmer/tracking';
import { later, cancel } from '@ember/runloop';
import { Promise } from 'rsvp';
import { set as emSet } from '@ember/object';
import { inject as service } from '@ember/service';
import { debug } from '@ember/debug';
import $ from "jquery";

class Content {
    @tracked user = null;
    @tracked page = null;
    @tracked ping = null;
    @tracked refresh = null;
    @tracked structure = null;
    @tracked rtoken = null;
    @tracked status = null;
    @tracked popup = null;
    @tracked tabs = [];
    @tracked navEntries = [];
    @tracked error = null;
    @tracked showLoadingBanner = false;
}

export default class OpenXpkiRoute extends Route {
    @service('oxi-config') oxiConfig;
    @service('oxi-locale') oxiLocale;
    @service('intl') intl;

    // Reserved Ember property "queryParams"
    // https://api.emberjs.com/ember/3.17/classes/Route/properties/queryParams?anchor=queryParams
    queryParams = {
        // refreshModel==true causes an "in-place" transition, so the model
        // hooks for this route (and any child routes) will re-fire
        startat:  { refreshModel: true },
        limit:    { refreshModel: true },
        force:    { refreshModel: true },
    };
    needReboot = ["login", "logout", "login!logout", "welcome"];

    @tracked content = new Content();
    quietRequest = false;

    // Reserved Ember function
    beforeModel(transition) {
        let queryParams = transition.to.queryParams;
        let modelId = transition.to.params.model_id;
        debug("openxpki/route - beforeModel: model_id = " + modelId);

        // "force" is only evaluated above using "refreshModel: true"
        if (queryParams.force) {
            delete queryParams.force;
        }

        let structureIfNeeded; // chain of ajax calls via Promises

        /*
         * load base page structure first first time or for special pages ("needReboot")
         */
        if (!this.content.navEntries.length || this.needReboot.indexOf(modelId) >= 0) {
            // don't send request yet, only create a lambda via arrow function expression
            structureIfNeeded = () => {
                return this.sendAjax({
                    page: "bootstrap!structure",
                    baseurl: window.location.pathname,
                });
            };
        }
        else {
            structureIfNeeded = () => { Promise.resolve() };
        }

        /*
         * load requested page part
         */
        let request = {
            page: modelId
        };
        if (queryParams.limit) { request.limit = queryParams.limit }
        if (queryParams.startat) { request.startat = queryParams.startat }

        // load as top content if 'modelId' is part of navigation or in 'needReboot' list
        let flatList = this.content.navEntries.reduce((p, n) => p.concat(n, n.entries || []), []);
        if (flatList.findBy("key", modelId) || this.needReboot.indexOf(modelId) >= 0) {
            request.target = "top";
        }
        return this.oxiConfig.ready // localconfig.js might change rootURL, so first thing is to query it
            .then( () => structureIfNeeded() )
            .then( () => this.sendAjax(request) );
    }

    // Reserved Ember function
    model(params/*, transition*/) {
        this.content.page = params.model_id; this.updateNavEntryActiveState(this.content);
        return this.content;
    }

    doPing(cfg) {
        this.content.ping = later(this, () => {
            $.ajax({ url: cfg.href });
            return this.doPing(cfg);
        }, cfg.timeout);
    }

    setLoadingState(isLoading) {
        // don't show optical hints if quietRequest
        if (this.quietRequest) return;

        // note that we cannot use the Ember "loading" event as this would only
        // trigger on route changes, but not if we do sendAjax()
        if (isLoading) {
            // remove focus from button to prevent user from doing another
            // submit by hitting enter
            document.activeElement.blur();
        }
        this.content.showLoadingBanner = isLoading;
    }

    // sends an AJAX request without showing "loading" banner or dimming page
    sendAjaxQuiet(request) {
        this.quietRequest = true;
        return this.sendAjax(request);
    }

    sendAjax(request) {
        this.quietRequest = false;
        this.setLoadingState(true);
        debug("openxpki/route - sendAjax: " + ['page','action'].map(p=>request[p]?`${p} = ${request[p]}`:null).filter(e=>e!==null).join(", "));
        // assemble request parameters
        let req = {
            data: {
                ...request,
                "_": new Date().getTime(),
            },
            dataType: "json",
            type: request.action ? "POST" : "GET",
            url: this.oxiConfig.backendUrl,
        };
        if (req.type === "POST") req.data._rtoken = this.content.rtoken;

        if (this.content.refresh) {
            cancel(this.content.refresh);
            this.content.refresh = null;
        }

        let realTarget = this._resolveTarget(req.data.target); // has to be done before "this.content.popup = null"

        /* eslint-disable-next-line no-unused-vars */
        return new Promise((resolve, reject) => {
            $.ajax(req).then(
                // SUCCESS
                doc => {
                    this.content.status = doc.status;
                    this.content.popup = null;

                    if (doc.ping) {
                        debug("openxpki/route - sendAjax response: \"ping\" " + doc.ping);
                        if (this.content.ping) { cancel(this.content.ping) }
                        this.doPing(doc.ping);
                    }
                    if (doc.refresh) {
                        debug("openxpki/route - sendAjax response: \"refresh\" " + doc.refresh.href + ", " + doc.refresh.timeout);
                        this.content.refresh = later(this, function() {
                            this.sendAjax({ page: doc.refresh.href });
                        }, doc.refresh.timeout);
                    }
                    if (doc.goto) {
                        debug("openxpki/route - sendAjax response: \"goto\" " + doc.goto);
                        if (doc.target === '_blank' || /^(http|\/)/.test(doc.goto)) {
                            window.location.href = doc.goto;
                        }
                        else {
                            this.transitionTo("openxpki", doc.goto);
                        }
                    }
                    else if (doc.structure) {
                        debug("openxpki/route - sendAjax response: \"structure\"");
                        this.content.navEntries = doc.structure; this.updateNavEntryActiveState();
                        this.content.user = doc.user;
                        this.content.rtoken = doc.rtoken;

                        // set locale
                        if (doc.language) this.oxiLocale.locale = doc.language;
                    }
                    else {
                        if (doc.page && doc.main) {
                            debug("openxpki/route - sendAjax response: \"page\" and \"main\"");
                            this._setPageContent(realTarget, doc.page, doc.main, doc.right);
                        }
                    }
                    this.setLoadingState(false);
                    return resolve(doc);
                },
                // FAILURE
                () => {
                    this.setLoadingState(false);
                    this.content.error = {
                        message: this.intl.t('error_popup.message')
                    };
                    return resolve({});
                }
            );
        });
    }

    _resolveTarget(requestTarget) {
        let target = requestTarget || 'self';
        // Pseudo-target "self" is transformed so new content will be shown in the
        // currently active place: a modal popup, an active tab or on top (i.e. single hidden tab)
        if (target === 'self') {
            if (this.content.popup) { target = 'popup' }
            else if (this.content.tabs.length > 1) { target = 'active' }
            else { target = 'top' }
        }
        if (target === 'modal') target = 'popup'; // FIXME remove support for legacy target 'modal'
        return target;
    }

    _setPageContent(target, page, main, right) {
        let newTab = {
            active: true,
            page: page,
            main: main,
            right: right
        };

        // Mark the first form on screen: only the first one is allowed to focus
        // its first input field.
        let isFirst = true;
        for (const section of [...(newTab.main||[]), ...(newTab.right||[])]) {
            if (section.type === "form") {
                section.content.isFirstForm = isFirst;
                if (isFirst) isFirst = false;
            }
        }

        // Popup
        if (target === "popup") {
            this.content.popup = newTab;
        }
        // New tab
        else if (target === "tab") {
            let tabs = this.content.tabs;
            tabs.setEach("active", false);
            tabs.pushObject(newTab);
        }
        // Current tab
        else if (target === "active") {
            let tabs = this.content.tabs;
            let index = tabs.indexOf(tabs.findBy("active"));
            tabs.replace(index, 1, [newTab]); // top
        }
        // Set as only tab
        else {
            this.content.tabs = [newTab];
        }
    }

    updateNavEntryActiveState() {
        let page = this.content.page;
        for (const entry of this.content.navEntries) {
            emSet(entry, "active", (entry.key === page));
            if (entry.entries) {
                entry.entries.setEach("active", false);
                let subEntry = entry.entries.findBy("key", page);
                if (subEntry) {
                    emSet(subEntry, "active", true);
                    emSet(entry, "active", true);
                }
            }
        }
        this.content.navEntries = this.content.navEntries; // trigger updates
    }
}<|endoftext|>


<commit_before>angular.module("umbraco").controller("FormEditor.Editor.DataController", ["$scope", "$filter", "$timeout", "assetsService", "dialogService", "angularHelper", "formEditorPropertyEditorResource", "editorState",
  function ($scope, $filter, $timeout, assetsService, dialogService, angularHelper, formEditorPropertyEditorResource, editorState) {
    assetsService.loadCss("/App_Plugins/FormEditor/css/form.css");

    // hide the property label?
    $scope.model.hideLabel = $scope.model.config.hideLabel == 1;

    // default sorting by date descending
    $scope.model.sortField = "_created";
    $scope.model.sortDescending = true;

    $scope.model.value = $scope.model.value || {};

    $scope.actionInProgress = false;
    $scope.dataState = "loading";

    $scope.expandedState = {
      visibleFields: {
        expanded: false
      }
    };

    $scope.hasSelection = function () {
      if (!$scope.model.data) {
        return false;
      }
      return _.find($scope.model.data.rows, function (row) {
        return row.selected == true;
      }) != null;
    }

    $scope.selectAll = function () {
      var select = !$scope.allSelected();
      _.each($scope.model.data.rows, function (row) {
        row.selected = select;
      });
    }

    $scope.allSelected = function () {
      if (!$scope.model.data) {
        return false;
      }
      return _.find($scope.model.data.rows, function (row) {
        return row.selected == false;
      }) == null;
    }

    $scope.getSelectedIds = function () {
      var ids = [];
      _.each($scope.model.data.rows, function (row) {
        if (row.selected == true) {
          ids.push(row._id);
        }
      });
      return ids;
    }

    $scope.getDocumentId = function () {
      return editorState.current.id;
    }

    $scope.sort = function (fieldName) {
      if ($scope.model.sortField == fieldName) {
        $scope.model.sortDescending = !$scope.model.sortDescending;
      }
      else {
        $scope.model.sortField = fieldName;
        $scope.model.sortDescending = false;
      }
      $scope.loadPage($scope.model.data.currentPage);
    }

    $scope.goToPage = function (page) {
      if (page <= 0 || page > $scope.model.data.totalPages || page == $scope.model.data.currentPage) {
        return;
      }

      $scope.loadPage(page);
    }

    $scope.loadPage = function (page) {
      $scope.actionInProgress = true;
      formEditorPropertyEditorResource.getData(editorState.current.id, page, $scope.model.sortField, $scope.model.sortDescending, $scope.model.searchQuery).then(function (data) {

        if (data == null || data.rows == null || data.rows.length == 0) {
          $scope.model.data = null;
          $scope.dataState = $scope.model.searchQuery ? $scope.dataState : "no-data";
          $scope.actionInProgress = false;
          return;
        }

        _.each(data.rows, function (row) {
          row.selected = false;
          // for reasons unknown the view messes up in-view date filters on documet save, so we'll execute the filters on load and use local properties per row
          row._createdDateShort = $filter("date")(row._createdDate, "yyyy-MM-dd");
          row._createdDateLong = $filter("date")(row._createdDate, "yyyy-MM-dd HH:mm:ss");
        });

        data.pages = [];
        for (var i = 1; i <= data.totalPages; i++) {
          data.pages.push(i);
        }

        $scope.supportsSearch = data.supportsSearch;
        $scope.supportsStatistics = data.supportsStatistics;
        $scope.actionInProgress = false;
        $scope.dataState = "data";
        $scope.model.data = data;

        // default to all fields visible
        if (!$scope.model.value.visibleFields) {
          $scope.model.value.visibleFields = [];
          _.each($scope.model.data.fields, function (field) {
            $scope.model.value.visibleFields.push(field.name);
          });
        }
        // clean up any fields that have been deleted from the form
        $scope.model.value.visibleFields = $filter("filter")($scope.model.value.visibleFields, function (fieldName, index, array) {
          return _.find($scope.model.data.fields, function (field) {
            return field.name == fieldName;
          }) != null;
        });
        $scope.actionInProgress = false;
      });
    }

    $scope.deleteSelected = function () {
      var ids = $scope.getSelectedIds();
      if (ids.length == 0) {
        return;
      }

      $scope.actionInProgress = true;

      formEditorPropertyEditorResource.deleteData(editorState.current.id, ids).then(function (data) {
        $scope.loadPage($scope.model.data.currentPage);
      });
    }

    $scope.viewEntry = function (index) {
      if ($scope.hasHiddenFields() == false) {
        return;
      }
      dialogService.open({
        dialogData: {
          row: $scope.model.data.rows[index],
          fields: $scope.model.data.fields
        },
        template: "data.viewEntry.html"
      });
    }

    $scope.selectFields = function () {
      dialogService.open({
        dialogData: {
          fieldConfigurations: _.map($scope.model.data.fields, function(field) {
            return {
              field: field,
              selected: $scope.isFieldVisible(field)
            };
          })
        },
        template: "data.selectFields.html",
        callback: function (dialogData) {
          console.log("callback", dialogData);
          $scope.model.value.visibleFields = _.map(_.where(dialogData.fieldConfigurations, { selected: true }), function(fieldConfiguration) {
            return fieldConfiguration.field.name;
          });
        }
      });
    }

    $scope.isFieldVisible = function (field) {
      return !$scope.model.value.visibleFields || $scope.model.value.visibleFields.indexOf(field.name) >= 0;
    }

    $scope.hasHiddenFields = function () {
      if (!$scope.model.data) {
        return false;
      }
      return $scope.model.value.visibleFields && $scope.model.value.visibleFields.length !== $scope.model.data.fields.length;
    }

    // helper to force the current form into the dirty state
    $scope.setDirty = function () {
      angularHelper.getCurrentForm($scope).$setDirty();
    }

    $scope.searchPromise = null;
    $scope.search = function () {
      if ($scope.searchPromise != null) {
        $timeout.cancel($scope.searchPromise);
      }
      $scope.searchPromise = $timeout(function () {
        $scope.loadPage(1);
      }, 600);
    }

    $scope.showStatistics = function () {
      dialogService.open({
        dialogData: {},
        template: "data.statistics.html",
        modalClass: "umb-modal stats-modal"
      });
    }

    $scope.loadPage(1);
  }
]);
<commit_msg>Submissions pagination improvements<commit_after>angular.module("umbraco").controller("FormEditor.Editor.DataController", ["$scope", "$filter", "$timeout", "assetsService", "dialogService", "angularHelper", "formEditorPropertyEditorResource", "editorState",
  function ($scope, $filter, $timeout, assetsService, dialogService, angularHelper, formEditorPropertyEditorResource, editorState) {
    assetsService.loadCss("/App_Plugins/FormEditor/css/form.css");

    // hide the property label?
    $scope.model.hideLabel = $scope.model.config.hideLabel == 1;

    // default sorting by date descending
    $scope.model.sortField = "_created";
    $scope.model.sortDescending = true;

    $scope.model.value = $scope.model.value || {};

    $scope.actionInProgress = false;
    $scope.dataState = "loading";

    $scope.expandedState = {
      visibleFields: {
        expanded: false
      }
    };

    $scope.hasSelection = function () {
      if (!$scope.model.data) {
        return false;
      }
      return _.find($scope.model.data.rows, function (row) {
        return row.selected == true;
      }) != null;
    }

    $scope.selectAll = function () {
      var select = !$scope.allSelected();
      _.each($scope.model.data.rows, function (row) {
        row.selected = select;
      });
    }

    $scope.allSelected = function () {
      if (!$scope.model.data) {
        return false;
      }
      return _.find($scope.model.data.rows, function (row) {
        return row.selected == false;
      }) == null;
    }

    $scope.getSelectedIds = function () {
      var ids = [];
      _.each($scope.model.data.rows, function (row) {
        if (row.selected == true) {
          ids.push(row._id);
        }
      });
      return ids;
    }

    $scope.getDocumentId = function () {
      return editorState.current.id;
    }

    $scope.sort = function (fieldName) {
      if ($scope.model.sortField == fieldName) {
        $scope.model.sortDescending = !$scope.model.sortDescending;
      }
      else {
        $scope.model.sortField = fieldName;
        $scope.model.sortDescending = false;
      }
      $scope.loadPage($scope.model.data.currentPage);
    }

    $scope.goToPage = function (page) {
      if (page <= 0 || page > $scope.model.data.totalPages || page == $scope.model.data.currentPage) {
        return;
      }

      $scope.loadPage(page);
    }

    $scope.loadPage = function (page) {
      $scope.actionInProgress = true;
      formEditorPropertyEditorResource.getData(editorState.current.id, page, $scope.model.sortField, $scope.model.sortDescending, $scope.model.searchQuery).then(function (data) {

        if (data == null || data.rows == null || data.rows.length == 0) {
          $scope.model.data = null;
          $scope.dataState = $scope.model.searchQuery ? $scope.dataState : "no-data";
          $scope.actionInProgress = false;
          return;
        }

        _.each(data.rows, function (row) {
          row.selected = false;
          // for reasons unknown the view messes up in-view date filters on documet save, so we'll execute the filters on load and use local properties per row
          row._createdDateShort = $filter("date")(row._createdDate, "yyyy-MM-dd");
          row._createdDateLong = $filter("date")(row._createdDate, "yyyy-MM-dd HH:mm:ss");
        });

        // make sure we don't end up with a gazillion pagination links if we have lots of submissions
        var start, end;
        var maxPages = 10;
        if (data.totalPages > maxPages) {
          start = page - maxPages / 2;
          if (start < 1) {
            start = 1;
          }
          end = start + maxPages;
          if (end > data.totalPages) {
            end = data.totalPages;
            start = end - maxPages;
          }
        }
        else {
          start = 1;
          end = data.totalPages;
        }
        data.pages = [];
        for (var i = start; i <= end; i++) {
          data.pages.push(i);
        }

        $scope.supportsSearch = data.supportsSearch;
        $scope.supportsStatistics = data.supportsStatistics;
        $scope.actionInProgress = false;
        $scope.dataState = "data";
        $scope.model.data = data;

        // default to all fields visible
        if (!$scope.model.value.visibleFields) {
          $scope.model.value.visibleFields = [];
          _.each($scope.model.data.fields, function (field) {
            $scope.model.value.visibleFields.push(field.name);
          });
        }
        // clean up any fields that have been deleted from the form
        $scope.model.value.visibleFields = $filter("filter")($scope.model.value.visibleFields, function (fieldName, index, array) {
          return _.find($scope.model.data.fields, function (field) {
            return field.name == fieldName;
          }) != null;
        });
        $scope.actionInProgress = false;
      });
    }

    $scope.deleteSelected = function () {
      var ids = $scope.getSelectedIds();
      if (ids.length == 0) {
        return;
      }

      $scope.actionInProgress = true;

      formEditorPropertyEditorResource.deleteData(editorState.current.id, ids).then(function (data) {
        $scope.loadPage($scope.model.data.currentPage);
      });
    }

    $scope.viewEntry = function (index) {
      if ($scope.hasHiddenFields() == false) {
        return;
      }
      dialogService.open({
        dialogData: {
          row: $scope.model.data.rows[index],
          fields: $scope.model.data.fields
        },
        template: "data.viewEntry.html"
      });
    }

    $scope.selectFields = function () {
      dialogService.open({
        dialogData: {
          fieldConfigurations: _.map($scope.model.data.fields, function(field) {
            return {
              field: field,
              selected: $scope.isFieldVisible(field)
            };
          })
        },
        template: "data.selectFields.html",
        callback: function (dialogData) {
          console.log("callback", dialogData);
          $scope.model.value.visibleFields = _.map(_.where(dialogData.fieldConfigurations, { selected: true }), function(fieldConfiguration) {
            return fieldConfiguration.field.name;
          });
        }
      });
    }

    $scope.isFieldVisible = function (field) {
      return !$scope.model.value.visibleFields || $scope.model.value.visibleFields.indexOf(field.name) >= 0;
    }

    $scope.hasHiddenFields = function () {
      if (!$scope.model.data) {
        return false;
      }
      return $scope.model.value.visibleFields && $scope.model.value.visibleFields.length !== $scope.model.data.fields.length;
    }

    // helper to force the current form into the dirty state
    $scope.setDirty = function () {
      angularHelper.getCurrentForm($scope).$setDirty();
    }

    $scope.searchPromise = null;
    $scope.search = function () {
      if ($scope.searchPromise != null) {
        $timeout.cancel($scope.searchPromise);
      }
      $scope.searchPromise = $timeout(function () {
        $scope.loadPage(1);
      }, 600);
    }

    $scope.showStatistics = function () {
      dialogService.open({
        dialogData: {},
        template: "data.statistics.html",
        modalClass: "umb-modal stats-modal"
      });
    }

    $scope.loadPage(1);
  }
]);
<|endoftext|>


<commit_before>global_vol = 0

//Change Qimessaging connection if robot name is passed in URL
if (robotAddress != '') {
    $.qim = new QiSession(robotAddress);
}

function get_robot_name() {
    $.getService('ALSystem', function(ALSystem) {
        ALSystem.robotName().done(
            function (name){
                get_robot_color(name);
                $('#robot_name').html("DJ " + name ).css({'color': colors[robot_color]});
            })
    });
}

function get_robot_icon() {
    $.getService('ALSystem', function(ALSystem) {
        ALSystem.robotIcon().done(
            function (buffer){
                $('#logo').html("");
            })
    });
}

function get_robot_color(name) {
    name = name.toLowerCase();
    switch (name) {
    case "raphael":
        robot_color = "red";
        break;
    case "michelangelo":
        robot_color = "orange";
        break;
    case "donatello":
        robot_color = "dpurp";
        break;
    case "leonardo":
        robot_color = "blue";
        break;
    default:
        var sum = $.map(name.split(""), function(c) {return c.charCodeAt()}).reduce(function(a, b) {return a + b})
        color_idx = sum % Object.keys(colors).length
        robot_color = Object.keys(colors)[color_idx]
    }
    $('body').css('background-color', colors[robot_color]);
    $('').appendTo("head");
}

function get_language() {
    $.getService('ALSpeechRecognition', function(ALSpeechRecognition) {
        ALSpeechRecognition.getLanguage().done(function (lang){
            var clang = $('#language_value').text();
            if (lang != clang) {
                $('#language_value').html(lang);
                switch(lang) {
                case "English":
                    switch_to_english();
                    break;
                case "Spanish":
                    switch_to_spanish();
                    break;
                case "French":
                    switch_to_french();
                    break;
                default:
                    console.log("Language currently unsupported; default to english");
                    switch_to_english();
                }
            }
        })
    });
}

function switch_to_english() {
    //
}

function switch_to_french() {
    //
}

function switch_to_spanish() {
    //
}

function generateQueue() {
    // Do stuff with queue
    $.getService('ALMusic', function(ALMusic) {
        ALMusic.getQueue().done(
            function(queue) {
                var generate_active = function() {
                    $('#active_song').fadeOut().empty();
                    for (song in queue['active']) {
                        var id = queue['active'][song]['id'];
                        var title = queue['active'][song]['title'];
                        var artist = queue['active'][song]['artist'];
                        var album = queue['active'][song]['album'];
                        var cover = queue['active'][song]['cover'];

                        $('#active_song').append(
                            ''+
                                '
' +
                                '
' +
                                '
' + title + '
'+
                                '
' + artist + '
'+
                                '
' + album + '
'+
                                '

').fadeIn();
                    }
                }
                var generate_queue = function() {
                    $('#dynamic_c').empty();
                    for (song in queue['queue']) {
                        var id = queue['queue'][song]['id'];
                        var title = queue['queue'][song]['title'];
                        var artist = queue['queue'][song]['artist'];

                        $('#dynamic_c').append(
                            '
' +
                                '
' +
                                '
' +
                                '' + title + '
' +
                                '
' +
                                '' + artist + '

' +
                                '
' +
                                '
' +
                                '
' +
                                '
' +
                                '
' +
                                '

' +
                                '
' +
                                '');
                        $('#' + id + '-Remove').click(function() {
                            queue_control('Remove', id);
                        });
                    }
                }
                generate_active();
                generate_queue();
            })
    });
}

/////////////////////////////
//     Search Handler     //
/////////////////////////////

function generateSearchResult(data) {
    $.getService('ALMusic', function(ALMusic) {
        ALMusic.search(data, 5).done(
            function(result){
                if(result.length < 1) {
                    $('#result_c').append(
                        '
' +
                            '
' +
                            'No results' +
                            '

');
                }
                else {
                    $('#result_c').empty();
                    for (song in result) {
                        var generate_result = function() {
                            var id = result[song]['id'];
                            var title = result[song]['title'];
                            var artist = result[song]['artist'];
                            var album = result[song]['album'];

                            $('#result_c').append(
                                '
' +
                                    '
' +
                                    '
' +
                                    '' + title + '
' +
                                    '
' +
                                    '' + artist + '

' +
                                    '
' +
                                    '
' +
                                    '');

                            $('#' + id + '-Add').click(function() {
                                spinID(id, true);
                                $.getService('ALMusic', function(ALMusic) {
                                    ALMusic.enqueueId(id).done(
                                        function(response) {
                                            spinID(id, false);
                                            if(Object.keys(response).length === 0){
                                                errorizeID(id);
                                                pulsateID(id);
                                            }
                                            else {
                                                $('#result_c').empty();
                                                $('#queue_add').val('');
                                            }
                                        })
                                });
                            });
                        }
                        generate_result();
                    }
                }
            })
    });
}

$("#am_search").click(function() {
    query = $('#queue_add').val();
    if (query.length > 0){
        generateSearchResult(query);
        // $('#queue_add').val('');
    }
    else {
        $('#result_c').empty();
    }
});

$("#queue_add").bind("enterKey",function() {
    query = $('#queue_add').val();
    if (query.length > 0){
        generateSearchResult(query);
        // $('#queue_add').val('');
    }
    else {
        $('#result_c').empty();
    }
});

$("#queue_add").keyup(function(e){
    if(e.keyCode == 13){
        $(this).trigger("enterKey");
    }
});

////////////////////
// Queue Handlers //
////////////////////

function queue_control(action, data) {
    $.getService('ALMusic', function(ALMusic) {
        switch(action) {
        case "Enqueue":
            spin(true)
            ALMusic.enqueue(data).done(function(song){
                console.log(song);
                spin(false);
                if(Object.keys(song).length === 0){
                    pulsate();
                }
            });
            break;
        case "Remove":
            ALMusic.remove(data).done(function(response) {
                console.log("remove song from queue")
            });
            break;
        case "Clear":
            ALMusic.clearQueue();
            break;
        case "Move_Up":
            console.log("Move Up functionality not implemented.");
            break;
        case "Move_Down":
            console.log("Move Down functionality not implemented.");
            break;
        }
    });
}

$("#am_enqueue").click(function() {
    query = $('#queue_add').val();
    if (query.length > 0){
        queue_control("Enqueue", query);
        $('#queue_add').val('');
        $('#result_c').empty();
    }
});

function spin(enable){
    if(enable){
        $('#add_icon').addClass('fa-spin');
    }
    else{
        $('#add_icon').removeClass('fa-spin');
    }
}

function spinID(id, enable){
    if(enable){
        $('#' + id  + '-Add').children(0).addClass('fa-spin');
    }
    else{
        $('#' + id + '-Add').children(0).removeClass('fa-spin');
    }
}

function pulsate(){
    $('#add_icon').effect('pulsate', {times:3}, 800);
}

function pulsateID(id){
    $('#' + id  + '-Add').children(0).effect('pulsate', {times:3}, 800);
}

function errorizeID(enable){
    if(enable){
        $('#' + id + '-Add').children(0).removeClass('fa-plus');
        $('#' + id  + '-Add').children(0).addClass('fa-exclamation-triangle');
    }
    else{
        $('#' + id + '-Add').children(0).removeClass('fa-exclamation-triangle');
        $('#' + id  + '-Add').children(0).addClass('fa-plus');
    }
}

$("#am_clear").click(function() {
    queue_control("Clear", null);
});

/////////////////////////////
// Control Button Handlers //
/////////////////////////////

function volume_control(action) {
    $.getService('ALAudioDevice', function(ALAudioDevice) {
        ALAudioDevice.getOutputVolume().done(
            function (volume) {
                ALAudioDevice.isAudioOutMuted().done(
                    function(muted){
                        if (muted) {
                            $('#am_vol_mute').css('color', '#000000');
                            ALAudioDevice.muteAudioOut(false);
                            volume = global_vol
                            ALAudioDevice.setOutputVolume(volume);
                        }
                        else {
                            switch(action) {
                            case "Up":
                                volume = volume + 5;
                                if (volume > 100) {
                                    volume = 100;
                                }
                                ALAudioDevice.setOutputVolume(volume);
                                break;
                            case "Down":
                                volume = volume - 5;
                                if (volume < 0) {
                                    volume = 0;
                                }
                                ALAudioDevice.setOutputVolume(volume);
                                break;
                            case "Mute":
                                global_vol = volume;
                                $('#am_vol_mute').css('color', colors[robot_color]);
                                ALAudioDevice.muteAudioOut(true);
                            }
                        }
                    })
            })
    });
}

$("#am_vol_mute").click(function() {
    volume_control("Mute");
});

$("#am_vol_down").click(function() {
    volume_control("Down");
});

$("#am_vol_up").click(function() {
    volume_control("Up");
});

function playback_control(action) {
    $.getService('ALMusic', function(ALMusic) {
        switch(action) {
        case "Play":
            ALMusic.play();
            break;
        case "Pause":
            console.log("Pause functionality not implemented.");
            break;
        case "Stop":
            ALMusic.stop();
            break;
        case "Next":
            ALMusic.next();
            break;
        }
    });
}

$("#am_play").click(function() {
    playback_control("Play");
});

$("#am_pause").click(function() {
    playback_control("Pause");
});

$("#am_stop").click(function() {
    playback_control("Stop");
});

$("#am_next").click(function() {
    playback_control("Next");
});

//////////////////////////

// ALMemory Subscriptions
$.subscribeToALMemoryEvent('ALMusic/onQueueChange', function(eventValue) {
    generateQueue();
});

// Connect/Disconnect signals
$.qim.socket().on('connect', init() );

function init() {
    $.when(get_robot_name()).done(function(){
        get_language();
    });
    get_robot_icon();
    generateQueue();
    $('body').fadeIn();
}

$.qim.socket().on('disconnect', function() {
    $('body').css('background-color','gray');
});
<commit_msg>fix errorize<commit_after>global_vol = 0

//Change Qimessaging connection if robot name is passed in URL
if (robotAddress != '') {
    $.qim = new QiSession(robotAddress);
}

function get_robot_name() {
    $.getService('ALSystem', function(ALSystem) {
        ALSystem.robotName().done(
            function (name){
                get_robot_color(name);
                $('#robot_name').html("DJ " + name ).css({'color': colors[robot_color]});
            })
    });
}

function get_robot_icon() {
    $.getService('ALSystem', function(ALSystem) {
        ALSystem.robotIcon().done(
            function (buffer){
                $('#logo').html("");
            })
    });
}

function get_robot_color(name) {
    name = name.toLowerCase();
    switch (name) {
    case "raphael":
        robot_color = "red";
        break;
    case "michelangelo":
        robot_color = "orange";
        break;
    case "donatello":
        robot_color = "dpurp";
        break;
    case "leonardo":
        robot_color = "blue";
        break;
    default:
        var sum = $.map(name.split(""), function(c) {return c.charCodeAt()}).reduce(function(a, b) {return a + b})
        color_idx = sum % Object.keys(colors).length
        robot_color = Object.keys(colors)[color_idx]
    }
    $('body').css('background-color', colors[robot_color]);
    $('').appendTo("head");
}

function get_language() {
    $.getService('ALSpeechRecognition', function(ALSpeechRecognition) {
        ALSpeechRecognition.getLanguage().done(function (lang){
            var clang = $('#language_value').text();
            if (lang != clang) {
                $('#language_value').html(lang);
                switch(lang) {
                case "English":
                    switch_to_english();
                    break;
                case "Spanish":
                    switch_to_spanish();
                    break;
                case "French":
                    switch_to_french();
                    break;
                default:
                    console.log("Language currently unsupported; default to english");
                    switch_to_english();
                }
            }
        })
    });
}

function switch_to_english() {
    //
}

function switch_to_french() {
    //
}

function switch_to_spanish() {
    //
}

function generateQueue() {
    // Do stuff with queue
    $.getService('ALMusic', function(ALMusic) {
        ALMusic.getQueue().done(
            function(queue) {
                var generate_active = function() {
                    $('#active_song').fadeOut().empty();
                    for (song in queue['active']) {
                        var id = queue['active'][song]['id'];
                        var title = queue['active'][song]['title'];
                        var artist = queue['active'][song]['artist'];
                        var album = queue['active'][song]['album'];
                        var cover = queue['active'][song]['cover'];

                        $('#active_song').append(
                            ''+
                                '
' +
                                '
' +
                                '
' + title + '
'+
                                '
' + artist + '
'+
                                '
' + album + '
'+
                                '

').fadeIn();
                    }
                }
                var generate_queue = function() {
                    $('#dynamic_c').empty();
                    for (song in queue['queue']) {
                        var id = queue['queue'][song]['id'];
                        var title = queue['queue'][song]['title'];
                        var artist = queue['queue'][song]['artist'];

                        $('#dynamic_c').append(
                            '
' +
                                '
' +
                                '
' +
                                '' + title + '
' +
                                '
' +
                                '' + artist + '

' +
                                '
' +
                                '
' +
                                '
' +
                                '
' +
                                '
' +
                                '

' +
                                '
' +
                                '');
                        $('#' + id + '-Remove').click(function() {
                            queue_control('Remove', id);
                        });
                    }
                }
                generate_active();
                generate_queue();
            })
    });
}

/////////////////////////////
//     Search Handler     //
/////////////////////////////

function generateSearchResult(data) {
    $.getService('ALMusic', function(ALMusic) {
        ALMusic.search(data, 5).done(
            function(result){
                if(result.length < 1) {
                    $('#result_c').append(
                        '
' +
                            '
' +
                            'No results' +
                            '

');
                }
                else {
                    $('#result_c').empty();
                    for (song in result) {
                        var generate_result = function() {
                            var id = result[song]['id'];
                            var title = result[song]['title'];
                            var artist = result[song]['artist'];
                            var album = result[song]['album'];

                            $('#result_c').append(
                                '
' +
                                    '
' +
                                    '
' +
                                    '' + title + '
' +
                                    '
' +
                                    '' + artist + '

' +
                                    '
' +
                                    '
' +
                                    '');

                            $('#' + id + '-Add').click(function() {
                                spinID(id, true);
                                $.getService('ALMusic', function(ALMusic) {
                                    ALMusic.enqueueId(id).done(
                                        function(response) {
                                            spinID(id, false);
                                            if(Object.keys(response).length === 0){
                                                errorizeID(id, true);
                                                pulsateID(id);
                                            }
                                            else {
                                                $('#result_c').empty();
                                                $('#queue_add').val('');
                                            }
                                        })
                                });
                            });
                        }
                        generate_result();
                    }
                }
            })
    });
}

$("#am_search").click(function() {
    query = $('#queue_add').val();
    if (query.length > 0){
        generateSearchResult(query);
        // $('#queue_add').val('');
    }
    else {
        $('#result_c').empty();
    }
});

$("#queue_add").bind("enterKey",function() {
    query = $('#queue_add').val();
    if (query.length > 0){
        generateSearchResult(query);
        // $('#queue_add').val('');
    }
    else {
        $('#result_c').empty();
    }
});

$("#queue_add").keyup(function(e){
    if(e.keyCode == 13){
        $(this).trigger("enterKey");
    }
});

////////////////////
// Queue Handlers //
////////////////////

function queue_control(action, data) {
    $.getService('ALMusic', function(ALMusic) {
        switch(action) {
        case "Enqueue":
            spin(true)
            ALMusic.enqueue(data).done(function(song){
                console.log(song);
                spin(false);
                if(Object.keys(song).length === 0){
                    pulsate();
                }
            });
            break;
        case "Remove":
            ALMusic.remove(data).done(function(response) {
                console.log("remove song from queue")
            });
            break;
        case "Clear":
            ALMusic.clearQueue();
            break;
        case "Move_Up":
            console.log("Move Up functionality not implemented.");
            break;
        case "Move_Down":
            console.log("Move Down functionality not implemented.");
            break;
        }
    });
}

$("#am_enqueue").click(function() {
    query = $('#queue_add').val();
    if (query.length > 0){
        queue_control("Enqueue", query);
        $('#queue_add').val('');
        $('#result_c').empty();
    }
});

function spin(enable){
    if(enable){
        $('#add_icon').addClass('fa-spin');
    }
    else{
        $('#add_icon').removeClass('fa-spin');
    }
}

function spinID(id, enable){
    if(enable){
        $('#' + id  + '-Add').children(0).addClass('fa-spin');
    }
    else{
        $('#' + id + '-Add').children(0).removeClass('fa-spin');
    }
}

function pulsate(){
    $('#add_icon').effect('pulsate', {times:3}, 800);
}

function pulsateID(id){
    $('#' + id  + '-Add').children(0).effect('pulsate', {times:3}, 800);
}

function errorizeID(id, enable){
    if(enable){
        $('#' + id + '-Add').children(0).removeClass('fa-plus');
        $('#' + id  + '-Add').children(0).addClass('fa-exclamation-triangle');
    }
    else{
        $('#' + id + '-Add').children(0).removeClass('fa-exclamation-triangle');
        $('#' + id  + '-Add').children(0).addClass('fa-plus');
    }
}

$("#am_clear").click(function() {
    queue_control("Clear", null);
});

/////////////////////////////
// Control Button Handlers //
/////////////////////////////

function volume_control(action) {
    $.getService('ALAudioDevice', function(ALAudioDevice) {
        ALAudioDevice.getOutputVolume().done(
            function (volume) {
                ALAudioDevice.isAudioOutMuted().done(
                    function(muted){
                        if (muted) {
                            $('#am_vol_mute').css('color', '#000000');
                            ALAudioDevice.muteAudioOut(false);
                            volume = global_vol
                            ALAudioDevice.setOutputVolume(volume);
                        }
                        else {
                            switch(action) {
                            case "Up":
                                volume = volume + 5;
                                if (volume > 100) {
                                    volume = 100;
                                }
                                ALAudioDevice.setOutputVolume(volume);
                                break;
                            case "Down":
                                volume = volume - 5;
                                if (volume < 0) {
                                    volume = 0;
                                }
                                ALAudioDevice.setOutputVolume(volume);
                                break;
                            case "Mute":
                                global_vol = volume;
                                $('#am_vol_mute').css('color', colors[robot_color]);
                                ALAudioDevice.muteAudioOut(true);
                            }
                        }
                    })
            })
    });
}

$("#am_vol_mute").click(function() {
    volume_control("Mute");
});

$("#am_vol_down").click(function() {
    volume_control("Down");
});

$("#am_vol_up").click(function() {
    volume_control("Up");
});

function playback_control(action) {
    $.getService('ALMusic', function(ALMusic) {
        switch(action) {
        case "Play":
            ALMusic.play();
            break;
        case "Pause":
            console.log("Pause functionality not implemented.");
            break;
        case "Stop":
            ALMusic.stop();
            break;
        case "Next":
            ALMusic.next();
            break;
        }
    });
}

$("#am_play").click(function() {
    playback_control("Play");
});

$("#am_pause").click(function() {
    playback_control("Pause");
});

$("#am_stop").click(function() {
    playback_control("Stop");
});

$("#am_next").click(function() {
    playback_control("Next");
});

//////////////////////////

// ALMemory Subscriptions
$.subscribeToALMemoryEvent('ALMusic/onQueueChange', function(eventValue) {
    generateQueue();
});

// Connect/Disconnect signals
$.qim.socket().on('connect', init() );

function init() {
    $.when(get_robot_name()).done(function(){
        get_language();
    });
    get_robot_icon();
    generateQueue();
    $('body').fadeIn();
}

$.qim.socket().on('disconnect', function() {
    $('body').css('background-color','gray');
});
<|endoftext|>


<commit_before>(function() {
    'use strict';

    var DIR         = __dirname + '/',
        DIR_HTML    = DIR       + 'html/',
        DIR_LIB     = DIR       + 'lib/',
        DIR_SERVER  = DIR       + 'lib/server/',
        DIR_FS      = DIR_HTML  + 'fs/',

        fs          = require('fs'),

        win         = require(DIR_SERVER + 'win'),
        main        = require(DIR_SERVER + 'main'),
        mellow      = require(DIR_SERVER + 'mellow'),
        ponse       = require(DIR_SERVER + 'ponse'),
        files       = require(DIR_SERVER + 'files'),
        update      = require(DIR_SERVER + 'update'),
        minify      = require(DIR_SERVER + 'minify'),
        rest        = require(DIR_SERVER + 'rest'),

        Util        = require(DIR_LIB + 'util'),
        CloudFunc   = require(DIR_LIB + 'cloudfunc'),
        server      = require(DIR_LIB + 'server'),
        format      = require(DIR_LIB + 'format'),

        Config      = main.config,

        PATH_INDEX  = DIR_FS   + 'index.html',

        TMPL_PATH   = [
            'file',
            'panel',
            'path',
            'pathLink',
            'link',
        ],

        Template    = {},

        FS          = CloudFunc.FS;

    exports.start = function(params) {
        readConfig(function(msg, config) {
            var keys;

            Util.log(msg);

            if (!config)
                config = {};

            if (params) {
                keys = Object.keys(params);

                keys.forEach(function(item) {
                    config[item] = params[item];
                });
            }

            init(config);

        });

        win.prepareCodePage();
    };

    /**
     * additional processing of index file
     */
    function indexProcessing(options) {
        var keysPanel, left, right,
            LEFT    = CloudFunc.PANEL_LEFT,
            RIGHT   = CloudFunc.PANEL_RIGHT,
            data    = options.data,
            panel   = options.panel;

        if (!Config.showKeysPanel) {
            keysPanel  = '<div class="keyspanel';
            data        = data.replace(keysPanel + '"', keysPanel +' hidden"');
        }

        left    = Util.render(Template.panel, {
            id      : LEFT,
            side    : 'left',
            content : panel
        });

        right    = Util.render(Template.panel, {
            id      : RIGHT,
            side    : 'right',
            content : panel
        });

        data = Util.render(data, {
            title   : CloudFunc.getTitle(),
            fm      : left + right
        });

        return data;
    }

    function init(config) {
        var paramsStart;

        Util.log('server dir: ' + DIR);

        if (update)
            update.get();

        if (config) {
            main.config = Config = config;

            if (config.test)
                Config.server  = false;
        }

        if (Config.logs) {
            Util.log('log param setted up in config.json\n' +
                'from now all logs will be writed to log.txt');

            writeLogsToFile();
        }

        paramsStart = {
            rest        : rest,
            route       : route
        };

        readFiles(paramsStart, function(params) {
            server.start(params);
        });
    }

    function readFiles(params, callback) {
        var filesList, paths   = {};

        filesList   = TMPL_PATH.map(function(name) {
            var path = DIR_FS + name + '.html';

            paths[path] = name;

            return path;
        });

        files.read(filesList, 'utf8', function(error, files) {
            var status, msg, names;

            if (error) {
                Util.log(error);
            } else {
                status          = 'ok';

                Object.keys(files).forEach(function(path) {
                    var name = paths[path];

                    Template[name] = files[path];
                });

                names           = TMPL_PATH.map(function(item) {
                    return item + '.html';
                });

                msg = CloudFunc.formatMsg('read', names, status);
                Util.log(msg);
            }

            callback(params);
        });
    }

    /**
     * routing of server queries
     */
    function route(request, response, callback) {
        var name, p, isAuth, isFS, path;

        if (request && response) {
            name    = ponse.getPathName(request);
            isAuth  = Util.strCmp(name, ['/auth', '/auth/github']);
            isFS    = Util.strCmp(name, '/') || Util.isContainStrAtBegin(name, FS);

            p       = {
                request     : request,
                response    : response,
                gzip        : true,
                name        : name
            };

            if (!isAuth && !isFS)
                Util.exec(callback);
            else if (isAuth) {
                Util.log('* Routing' + '-> ' + name);

                p.name = DIR_HTML + name + '.html';
                ponse.sendFile(p);
            } else if (isFS) {
                name    = Util.rmStrOnce(name, CloudFunc.FS) || main.SLASH;
                path    = mellow.convertPath(name);

                mellow.read(path, function(error, dir) {
                    if (dir)
                        dir.path = format.addSlashToEnd(name);

                    if (error)
                        if (error.code !== 'ENOTDIR')
                            ponse.sendError(error, p);
                        else
                            fs.realpath(path, function(error, pathReal) {
                                if (!error)
                                    p.name = pathReal;
                                else
                                    p.name = path;

                                p.gzip = false;
                                ponse.sendFile(p);
                            });
                    else
                        buildIndex(dir, function(error, data) {
                            var NOT_LOG = true;

                            p.name = PATH_INDEX;

                            if (error)
                                ponse.sendError(error, p);
                            else
                                ponse.send(data, p, NOT_LOG);
                        });
                });
            }
        }
    }

    function buildIndex(json, callback) {
        var isMinify = Config.minify;

        Util.exec.if(!isMinify, function(error, name) {
            fs.readFile(name || PATH_INDEX, 'utf8', function(error, template) {
                var panel, data;

                if (!error) {
                    panel   = CloudFunc.buildFromJSON({
                        data        : json,
                        template    : Template
                    }),

                    data    = indexProcessing({
                        panel   : panel,
                        data    : template,
                    });
                }

                Util.exec(callback, error, data);
            });
        },  function(callback) {
                minify(PATH_INDEX, {
                    log         : true,
                    returnName  : true
                }, callback);
        });
    }

    function readConfig(callback) {
        var path = DIR + 'json/config.json';

        Util.checkArgs(arguments, ['callback']);

        fs.readFile(path, 'utf8', function(error, data) {
            var status, config, msg;

            if (error) {
                status  = 'error';
            } else {
                status  = 'ok';
                config  = Util.parseJSON(data);
            }

            msg         = CloudFunc.formatMsg('read', 'config', status);

            callback(msg, config);
        });
    }

    /* function sets stdout to file log.txt */
    function writeLogsToFile() {
        var stdout      = process.stdout,
            writeFile   = fs.createWriteStream('log.txt'),
            write       = writeFile.write.bind(writeFile);

        stdout.write    = write;
    }
})();
<commit_msg>refactor(cloudcmd) readConfig: readFile -> tryRequire<commit_after>(function() {
    'use strict';

    var DIR         = __dirname + '/',
        DIR_HTML    = DIR       + 'html/',
        DIR_LIB     = DIR       + 'lib/',
        DIR_SERVER  = DIR       + 'lib/server/',
        DIR_FS      = DIR_HTML  + 'fs/',

        fs          = require('fs'),

        win         = require(DIR_SERVER + 'win'),
        main        = require(DIR_SERVER + 'main'),
        mellow      = require(DIR_SERVER + 'mellow'),
        ponse       = require(DIR_SERVER + 'ponse'),
        files       = require(DIR_SERVER + 'files'),
        update      = require(DIR_SERVER + 'update'),
        minify      = require(DIR_SERVER + 'minify'),
        rest        = require(DIR_SERVER + 'rest'),
        tryRequire  = require(DIR_SERVER + 'tryRequire'),

        Util        = require(DIR_LIB + 'util'),
        CloudFunc   = require(DIR_LIB + 'cloudfunc'),
        server      = require(DIR_LIB + 'server'),
        format      = require(DIR_LIB + 'format'),

        Config      = main.config,

        PATH_INDEX  = DIR_FS   + 'index.html',

        TMPL_PATH   = [
            'file',
            'panel',
            'path',
            'pathLink',
            'link',
        ],

        Template    = {},

        FS          = CloudFunc.FS;

    exports.start = function(params) {
        readConfig(function(error, config) {
            var keys;

            if (error)
                Util.log(error.message);

            if (!config)
                config = {};

            if (params) {
                keys = Object.keys(params);

                keys.forEach(function(item) {
                    config[item] = params[item];
                });
            }

            init(config);
        });

        win.prepareCodePage();
    };

    /**
     * additional processing of index file
     */
    function indexProcessing(options) {
        var keysPanel, left, right,
            LEFT    = CloudFunc.PANEL_LEFT,
            RIGHT   = CloudFunc.PANEL_RIGHT,
            data    = options.data,
            panel   = options.panel;

        if (!Config.showKeysPanel) {
            keysPanel  = '<div class="keyspanel';
            data        = data.replace(keysPanel + '"', keysPanel +' hidden"');
        }

        left    = Util.render(Template.panel, {
            id      : LEFT,
            side    : 'left',
            content : panel
        });

        right    = Util.render(Template.panel, {
            id      : RIGHT,
            side    : 'right',
            content : panel
        });

        data = Util.render(data, {
            title   : CloudFunc.getTitle(),
            fm      : left + right
        });

        return data;
    }

    function init(config) {
        var paramsStart;

        Util.log('server dir: ' + DIR);

        if (update)
            update.get();

        if (config) {
            main.config = Config = config;

            if (config.test)
                Config.server  = false;
        }

        if (Config.logs) {
            Util.log('log param setted up in config.json\n' +
                'from now all logs will be writed to log.txt');

            writeLogsToFile();
        }

        paramsStart = {
            rest        : rest,
            route       : route
        };

        readFiles(paramsStart, function(params) {
            server.start(params);
        });
    }

    function readFiles(params, callback) {
        var filesList, paths   = {};

        filesList   = TMPL_PATH.map(function(name) {
            var path = DIR_FS + name + '.html';

            paths[path] = name;

            return path;
        });

        files.read(filesList, 'utf8', function(error, files) {
            var status, msg, names;

            if (error) {
                Util.log(error);
            } else {
                status          = 'ok';

                Object.keys(files).forEach(function(path) {
                    var name = paths[path];

                    Template[name] = files[path];
                });

                names           = TMPL_PATH.map(function(item) {
                    return item + '.html';
                });

                msg = CloudFunc.formatMsg('read', names, status);
                Util.log(msg);
            }

            callback(params);
        });
    }

    /**
     * routing of server queries
     */
    function route(request, response, callback) {
        var name, p, isAuth, isFS, path;

        if (request && response) {
            name    = ponse.getPathName(request);
            isAuth  = Util.strCmp(name, ['/auth', '/auth/github']);
            isFS    = Util.strCmp(name, '/') || Util.isContainStrAtBegin(name, FS);

            p       = {
                request     : request,
                response    : response,
                gzip        : true,
                name        : name
            };

            if (!isAuth && !isFS)
                Util.exec(callback);
            else if (isAuth) {
                Util.log('* Routing' + '-> ' + name);

                p.name = DIR_HTML + name + '.html';
                ponse.sendFile(p);
            } else if (isFS) {
                name    = Util.rmStrOnce(name, CloudFunc.FS) || main.SLASH;
                path    = mellow.convertPath(name);

                mellow.read(path, function(error, dir) {
                    if (dir)
                        dir.path = format.addSlashToEnd(name);

                    if (error)
                        if (error.code !== 'ENOTDIR')
                            ponse.sendError(error, p);
                        else
                            fs.realpath(path, function(error, pathReal) {
                                if (!error)
                                    p.name = pathReal;
                                else
                                    p.name = path;

                                p.gzip = false;
                                ponse.sendFile(p);
                            });
                    else
                        buildIndex(dir, function(error, data) {
                            var NOT_LOG = true;

                            p.name = PATH_INDEX;

                            if (error)
                                ponse.sendError(error, p);
                            else
                                ponse.send(data, p, NOT_LOG);
                        });
                });
            }
        }
    }

    function buildIndex(json, callback) {
        var isMinify = Config.minify;

        Util.exec.if(!isMinify, function(error, name) {
            fs.readFile(name || PATH_INDEX, 'utf8', function(error, template) {
                var panel, data;

                if (!error) {
                    panel   = CloudFunc.buildFromJSON({
                        data        : json,
                        template    : Template
                    }),

                    data    = indexProcessing({
                        panel   : panel,
                        data    : template,
                    });
                }

                Util.exec(callback, error, data);
            });
        },  function(callback) {
                minify(PATH_INDEX, {
                    log         : true,
                    returnName  : true
                }, callback);
        });
    }

    function readConfig(callback) {
        var error,
            configPath  = DIR + 'json/config.json',
            config      = tryRequire(configPath, function(err) {
                if (err)
                    error = err;
            });

        Util.checkArgs(arguments, ['callback']);

        callback(error, config);
    }

    /* function sets stdout to file log.txt */
    function writeLogsToFile() {
        var stdout      = process.stdout,
            writeFile   = fs.createWriteStream('log.txt'),
            write       = writeFile.write.bind(writeFile);

        stdout.write    = write;
    }
})();
<|endoftext|>


<commit_before>const temp = require('./temp-helper');
const ElasticAssert = require('@cardstack/elasticsearch/node-tests/assertions');
const JSONAPIFactory = require('./jsonapi-factory');
const crypto = require('crypto');
const { wireItUp } = require('@cardstack/hub/main');
const Session = require('@cardstack/plugin-utils/session');

exports.createDefaultEnvironment = async function(projectDir, initialModels = []) {
  let container;
  try {
    let factory = new JSONAPIFactory();

    let user = factory.addResource('users', 'the-default-test-user').withAttributes({
      fullName: 'Default Test Environment',
      email: 'test@example.com'
    }).asDocument();

    let session = new Session(
      { id: 'the-default-test-user', type: 'users'},
      null,
      user
    );

    factory.addResource('plugin-configs', '@cardstack/hub')
      .withRelated(
        'default-data-source',
        factory.addResource('data-sources')
          .withAttributes({
            'source-type': '@cardstack/ephemeral'
          })
      );

    factory.addResource('grants')
      .withAttributes({
        mayCreateResource: true,
        mayUpdateResource: true,
        mayDeleteResource: true,
        mayWriteField: true
      }).withRelated('who', factory.addResource('groups', user.data.id));

    container = await wireItUp(projectDir, crypto.randomBytes(32), factory.getModels(), {
      allowDevDependencies: true,
      disableAutomaticIndexing: true
    });

    let writers = container.lookup('hub:writers');

    for (let model of initialModels) {
      // TODO: this one-by-one creation is still slower than is nice for
      // tests -- each time we write a schema model it invalidates the
      // schema cache, which needs to get rebuilt before we can write
      // the next one.
      //
      // On the other hand, this is a high-quality test of our ability
      // to build up the entire state in the same way an end user would
      // via JSONAPI requests. If we optimize this away, we should also
      // add some tests like that that are similarly comprehensive.
      await writers.create('master', session, model.type, model);
    }

    await container.lookup('hub:indexers').update({ realTime: true });

    Object.assign(container, {
      session,
      user,
      async setUserId(id) {
        let plugins = await this.lookup('hub:plugins').active();
        let m = plugins.lookupFeatureAndAssert('middleware', '@cardstack/test-support-authenticator');
        m.userId = id;
      }
    });
    return container;
  } catch (err) {
    // don't leave a half-constructed environment lying around if we
    // failed. Cleanup whatever parts were already created.
    if (container) {
      container.teardown();
    }
    destroyIndices();
    temp.cleanup();
    throw err;
  }
};

  exports.destroyDefaultEnvironment = async function(env) {
    if (env) {
      await env.teardown();
      await destroyIndices();
      await temp.cleanup();
    }
  };

  async function destroyIndices() {
    let ea = new ElasticAssert();
    await ea.deleteContentIndices();
  }
<commit_msg>simplify test harness using features in ephemeral storage plugin<commit_after>const temp = require('./temp-helper');
const ElasticAssert = require('@cardstack/elasticsearch/node-tests/assertions');
const JSONAPIFactory = require('./jsonapi-factory');
const crypto = require('crypto');
const { wireItUp } = require('@cardstack/hub/main');
const Session = require('@cardstack/plugin-utils/session');

exports.createDefaultEnvironment = async function(projectDir, initialModels = []) {
  let container;
  try {
    let factory = new JSONAPIFactory();

    let user = factory.addResource('users', 'the-default-test-user').withAttributes({
      fullName: 'Default Test Environment',
      email: 'test@example.com'
    }).asDocument();

    let session = new Session(
      { id: 'the-default-test-user', type: 'users'},
      null,
      user
    );

    factory.addResource('plugin-configs', '@cardstack/hub')
      .withRelated(
        'default-data-source',
        factory.addResource('data-sources')
          .withAttributes({
            'source-type': '@cardstack/ephemeral',
            params: { initialModels }
          })
      );

    factory.addResource('grants')
      .withAttributes({
        mayCreateResource: true,
        mayUpdateResource: true,
        mayDeleteResource: true,
        mayWriteField: true
      }).withRelated('who', factory.addResource('groups', user.data.id));

    container = await wireItUp(projectDir, crypto.randomBytes(32), factory.getModels(), {
      allowDevDependencies: true,
      disableAutomaticIndexing: true
    });

    await container.lookup('hub:indexers').update({ realTime: true });

    Object.assign(container, {
      session,
      user,
      async setUserId(id) {
        let plugins = await this.lookup('hub:plugins').active();
        let m = plugins.lookupFeatureAndAssert('middleware', '@cardstack/test-support-authenticator');
        m.userId = id;
      }
    });
    return container;
  } catch (err) {
    // don't leave a half-constructed environment lying around if we
    // failed. Cleanup whatever parts were already created.
    if (container) {
      container.teardown();
    }
    destroyIndices();
    temp.cleanup();
    throw err;
  }
};

  exports.destroyDefaultEnvironment = async function(env) {
    if (env) {
      await env.teardown();
      await destroyIndices();
      await temp.cleanup();
    }
  };

  async function destroyIndices() {
    let ea = new ElasticAssert();
    await ea.deleteContentIndices();
  }
<|endoftext|>


<commit_before>#! /usr/bin/env node

/* A script for converting javascript challenge code into JSON.
  * Each challenge should have sections marked with comments '/// keyword:',
  * For example:
  *   /// solution:
  *   function MySolution()...
  *
  *   /// tests:
  *   assert(MySolution(2) === 3)...
  *
  * */

const commandLineArgs = require('command-line-args');
const fs = require('fs');
const acorn = require('acorn');

const optionDefinitions = [
  { name: 'force', type: Boolean, descr: 'Overwrite output files forcefully.' },
  { name: 'help', alias: 'h', type: Boolean, descr: 'Print help message' },
  { name: 'infile', alias: 'f', type: String, multiple: true, descr: 'Input files' },
  { name: 'outfile', alias: 'o', type: String, descr: 'Output JSON file' },
  { name: 'verbose', alias: 'v', type: Boolean, descr: 'Run in verbose mode' }
];

// Regexp definitions for the script
const re = {
  lineComment: /^\/\/\//,
  lineCommentWithProp: /^\/\/\/\s+(\w+):\s*$/,
  lineCommentWithPropAndText: /^\/\/\/\s+(\w+):\s*(\S.*)$/,
  lineCommentWithText: /^\/\/\/\s(.*)$/,
  endComment: /^\/\/\/\s+end\s+$/
};

const opts = commandLineArgs(optionDefinitions);

if (opts.help) {
  usage(0);
}

const expectedProps = {
  title: String,
  type: String,
  description: String,
  challengeSeed: 'Code',
  solutions: 'Code',
  tests: 'Code'
};

if (!opts.infile || opts.infile.length === 0) {
  console.error('Error. No input files were given.');
  usage(1);
}

const propParser = {
  state: 'IDLE',
  currFile: null,
  prop: null,
  propValue: [],
  files: {},
  continuedComments: false
};

opts.infile.forEach(file => {
  processFile(propParser, file, expectedProps);
});

const result = formatResult(propParser.files);

printToOutput(result);

//---------------------------------------------------------------------------
// HELPER FUNCTIONS
//---------------------------------------------------------------------------

function processFile(parser, file, props) {
  const buffer = fs.readFileSync(file);
  parser.currFile = file;
  parser.files[file] = {};
  checkFileSyntax(buffer);

  const lines = buffer.toString().split('\n');
  lines.forEach(line => {
    processLine(parser, line);
  });

  if (parser.propValue.length > 0) {
    finishCurrProp(parser);
  }

  verifyExpectedProps(parser, file, props);
}

/* Checks the code syntax in given buffer using acorn. */
function checkFileSyntax(buffer) {
  try {
    acorn.parse(buffer);
  }
  catch (err) {
    console.error(`Parsing failed: ${err}`);
    process.exit(1);
  }
}

/* Processes one line with regex and extracts prop values. */
function processLine(parser, line) {
  if (parser.continuedComments && !re.lineComment.test(line)) {
    finishCurrProp(parser);
    return;
  }
  parser.continuedComments = false;

  if (re.lineCommentWithProp.test(line)) {
    const matches = line.match(re.lineCommentWithProp);
    if (parser.prop !== null) {
      finishCurrProp(parser);
    }
    parser.prop = matches[1];
  }
  else if (re.lineCommentWithPropAndText.test(line)) {
    const matches = line.match(re.lineCommentWithPropAndText);
    parser.prop = matches[1];
    parser.propValue.push(matches[2]);
    finishCurrProp(parser);
  }
  else if (re.lineCommentWithText.test(line)) {
    const matches = line.match(re.lineCommentWithText);
    parser.propValue.push(matches[1]);
    parser.continuedComments = true;
  }
  else if (re.endComment.test(line)) {
    finishCurrProp(parser);
  }
  else if (parser.prop !== null) {
    parser.propValue.push(line);
    // if prop is solutions, combine strings into one string with newline
    if (parser.prop === 'solutions' || parser.prop === 'naive') {
      if (parser.propValue.length > 1) {
        parser.propValue = [parser.propValue.join('\n')];
      }
    }
  }
}

/* Adds current property value into the parser for the current file being processed. Resets
 * the prop value after this. */
function finishCurrProp(parser) {
  // shave off ending empty string from arrays:
  if (parser.propValue.slice(-1).toString() === '') {
    parser.propValue.splice(-1);
  }

  let newPropVal;

  // keep solutions string in an array
  if (parser.prop === 'solutions') {
    newPropVal = parser.propValue;
  }
  else if (parser.propValue.length === 1) {
    newPropVal = parser.propValue[0] || parser.propValue;
  }
  else newPropVal = parser.propValue;

  if (typeof parser.files[parser.currFile][parser.prop] === 'undefined') {
    parser.files[parser.currFile][parser.prop] = newPropVal;
    parser.files[parser.currFile][parser.prop].num = 1;
  }
  else if (typeof parser.files[parser.currFile][parser.prop] === 'string') {
    const oldValue = parser.files[parser.currFile][parser.prop];
    parser.files[parser.currFile][parser.prop] = [oldValue, newPropVal];
    parser.files[parser.currFile][parser.prop].num = 2;
  }
  else if (typeof parser.files[parser.currFile][parser.prop] === 'object') {
    if (parser.files[parser.currFile][parser.prop].num === 1) {
      // Need to wrap the value in an array first.
      parser.files[parser.currFile][parser.prop] = [
        parser.files[parser.currFile][parser.prop],
        newPropVal
      ];
      parser.files[parser.currFile][parser.prop].num = 2;
    }
    else {
      parser.files[parser.currFile][parser.prop].push(newPropVal);
      parser.files[parser.currFile][parser.prop].num += 1;
    }
  }

  parser.propValue = [];
  parser.prop = null;
}

/* Verifies that the parsed file contains expected props like 'solution' etc.*/
function verifyExpectedProps(parser, file, props) {
  Object.keys(props).forEach(prop => {
    if (parser.files[file][prop] === undefined) {
      console.warn(`Prop ${prop} missing from file ${file}`);
    }
  });
}

/* Formats the result as JSON. */
function formatResult(parsedFiles) {
  const challenges = Object.keys(parsedFiles).map(filename =>
    parsedFiles[filename]
  );

  challenges.forEach(challenge => {
    challenge.id = parseInt(Math.random(), 16);
  });

  const finalFormat = {
    name: 'ArcadeMode Interview Questions',
    order: '',
    time: '',
    helpRoom: '',
    challenges
  };

  return JSON.stringify(finalFormat, null, 2);
}

/* Prints the results to given output file. */
function printToOutput (res) {
  if (opts.outfile) {
    if (!fs.existsSync(opts.outfile) || opts.force) {
      fs.writeFileSync(opts.outfile, res);
    }
  }
  else {
    console.log(res);
  }
}

/* Prints the usage of the script and exits. */
function usage(exitCode = 0) {
  console.log('Usage: bin/js2json_challenges.js [opts] -f <files>+\n');
  optionDefinitions.forEach(opt => {
    if (opt.alias) {
      console.log(`\t--${opt.name},-${opt.alias}\t- ${opt.descr}`);
    }
    else {
      console.log(`\t--${opt.name}\t\t- ${opt.descr}`);
    }
  });
  process.exit(exitCode);
}
<commit_msg>add TODO note for fix<commit_after>#! /usr/bin/env node

/* A script for converting javascript challenge code into JSON.
  * Each challenge should have sections marked with comments '/// keyword:',
  * For example:
  *   /// solution:
  *   function MySolution()...
  *
  *   /// tests:
  *   assert(MySolution(2) === 3)...
  *
  * */

// TODO: fix script to remove empty string at end of the last property (i.e., test),
// otherwise an empty test case is added which is problematic.

const commandLineArgs = require('command-line-args');
const fs = require('fs');
const acorn = require('acorn');

const optionDefinitions = [
  { name: 'force', type: Boolean, descr: 'Overwrite output files forcefully.' },
  { name: 'help', alias: 'h', type: Boolean, descr: 'Print help message' },
  { name: 'infile', alias: 'f', type: String, multiple: true, descr: 'Input files' },
  { name: 'outfile', alias: 'o', type: String, descr: 'Output JSON file' },
  { name: 'verbose', alias: 'v', type: Boolean, descr: 'Run in verbose mode' }
];

// Regexp definitions for the script
const re = {
  lineComment: /^\/\/\//,
  lineCommentWithProp: /^\/\/\/\s+(\w+):\s*$/,
  lineCommentWithPropAndText: /^\/\/\/\s+(\w+):\s*(\S.*)$/,
  lineCommentWithText: /^\/\/\/\s(.*)$/,
  endComment: /^\/\/\/\s+end\s+$/
};

const opts = commandLineArgs(optionDefinitions);

if (opts.help) {
  usage(0);
}

const expectedProps = {
  title: String,
  type: String,
  description: String,
  challengeSeed: 'Code',
  solutions: 'Code',
  tests: 'Code'
};

if (!opts.infile || opts.infile.length === 0) {
  console.error('Error. No input files were given.');
  usage(1);
}

const propParser = {
  state: 'IDLE',
  currFile: null,
  prop: null,
  propValue: [],
  files: {},
  continuedComments: false
};

opts.infile.forEach(file => {
  processFile(propParser, file, expectedProps);
});

const result = formatResult(propParser.files);

printToOutput(result);

//---------------------------------------------------------------------------
// HELPER FUNCTIONS
//---------------------------------------------------------------------------

function processFile(parser, file, props) {
  const buffer = fs.readFileSync(file);
  parser.currFile = file;
  parser.files[file] = {};
  checkFileSyntax(buffer);

  const lines = buffer.toString().split('\n');
  lines.forEach(line => {
    processLine(parser, line);
  });

  if (parser.propValue.length > 0) {
    finishCurrProp(parser);
  }

  verifyExpectedProps(parser, file, props);
}

/* Checks the code syntax in given buffer using acorn. */
function checkFileSyntax(buffer) {
  try {
    acorn.parse(buffer);
  }
  catch (err) {
    console.error(`Parsing failed: ${err}`);
    process.exit(1);
  }
}

/* Processes one line with regex and extracts prop values. */
function processLine(parser, line) {
  if (parser.continuedComments && !re.lineComment.test(line)) {
    finishCurrProp(parser);
    return;
  }
  parser.continuedComments = false;

  if (re.lineCommentWithProp.test(line)) {
    const matches = line.match(re.lineCommentWithProp);
    if (parser.prop !== null) {
      finishCurrProp(parser);
    }
    parser.prop = matches[1];
  }
  else if (re.lineCommentWithPropAndText.test(line)) {
    const matches = line.match(re.lineCommentWithPropAndText);
    parser.prop = matches[1];
    parser.propValue.push(matches[2]);
    finishCurrProp(parser);
  }
  else if (re.lineCommentWithText.test(line)) {
    const matches = line.match(re.lineCommentWithText);
    parser.propValue.push(matches[1]);
    parser.continuedComments = true;
  }
  else if (re.endComment.test(line)) {
    finishCurrProp(parser);
  }
  else if (parser.prop !== null) {
    parser.propValue.push(line);
    // if prop is solutions, combine strings into one string with newline
    if (parser.prop === 'solutions' || parser.prop === 'naive') {
      if (parser.propValue.length > 1) {
        parser.propValue = [parser.propValue.join('\n')];
      }
    }
  }
}

/* Adds current property value into the parser for the current file being processed. Resets
 * the prop value after this. */
function finishCurrProp(parser) {
  // shave off ending empty string from arrays:
  if (parser.propValue.slice(-1).toString() === '') {
    parser.propValue.splice(-1);
  }

  let newPropVal;

  // keep solutions string in an array
  if (parser.prop === 'solutions') {
    newPropVal = parser.propValue;
  }
  else if (parser.propValue.length === 1) {
    newPropVal = parser.propValue[0] || parser.propValue;
  }
  else newPropVal = parser.propValue;

  if (typeof parser.files[parser.currFile][parser.prop] === 'undefined') {
    parser.files[parser.currFile][parser.prop] = newPropVal;
    parser.files[parser.currFile][parser.prop].num = 1;
  }
  else if (typeof parser.files[parser.currFile][parser.prop] === 'string') {
    const oldValue = parser.files[parser.currFile][parser.prop];
    parser.files[parser.currFile][parser.prop] = [oldValue, newPropVal];
    parser.files[parser.currFile][parser.prop].num = 2;
  }
  else if (typeof parser.files[parser.currFile][parser.prop] === 'object') {
    if (parser.files[parser.currFile][parser.prop].num === 1) {
      // Need to wrap the value in an array first.
      parser.files[parser.currFile][parser.prop] = [
        parser.files[parser.currFile][parser.prop],
        newPropVal
      ];
      parser.files[parser.currFile][parser.prop].num = 2;
    }
    else {
      parser.files[parser.currFile][parser.prop].push(newPropVal);
      parser.files[parser.currFile][parser.prop].num += 1;
    }
  }

  parser.propValue = [];
  parser.prop = null;
}

/* Verifies that the parsed file contains expected props like 'solution' etc.*/
function verifyExpectedProps(parser, file, props) {
  Object.keys(props).forEach(prop => {
    if (parser.files[file][prop] === undefined) {
      console.warn(`Prop ${prop} missing from file ${file}`);
    }
  });
}

/* Formats the result as JSON. */
function formatResult(parsedFiles) {
  const challenges = Object.keys(parsedFiles).map(filename =>
    parsedFiles[filename]
  );

  challenges.forEach(challenge => {
    challenge.id = parseInt(Math.random(), 16);
  });

  const finalFormat = {
    name: 'ArcadeMode Interview Questions',
    order: '',
    time: '',
    helpRoom: '',
    challenges
  };

  return JSON.stringify(finalFormat, null, 2);
}

/* Prints the results to given output file. */
function printToOutput (res) {
  if (opts.outfile) {
    if (!fs.existsSync(opts.outfile) || opts.force) {
      fs.writeFileSync(opts.outfile, res);
    }
  }
  else {
    console.log(res);
  }
}

/* Prints the usage of the script and exits. */
function usage(exitCode = 0) {
  console.log('Usage: bin/js2json_challenges.js [opts] -f <files>+\n');
  optionDefinitions.forEach(opt => {
    if (opt.alias) {
      console.log(`\t--${opt.name},-${opt.alias}\t- ${opt.descr}`);
    }
    else {
      console.log(`\t--${opt.name}\t\t- ${opt.descr}`);
    }
  });
  process.exit(exitCode);
}
<|endoftext|>