<commit_before>import $ from "jquery";

import render_dialog_widget from "../templates/dialog_widget.hbs";
import render_dialog_heading from "../templates/dialog_widget_heading.hbs";

import * as blueslip from "./blueslip";
import {$t_html} from "./i18n";
import * as loading from "./loading";
import * as overlays from "./overlays";
import * as settings_data from "./settings_data";

/*
    Look for dialog_widget in settings_users
    to see an example of how to use this widget.  It's
    pretty simple to use!

    Some things to note:

        1) We create DOM on the fly, and we remove
           the DOM once it's closed.

        2) We attach the DOM for the modal to conf.parent,
           and this temporary DOM location will influence
           how styles work.

        3) The cancel button is driven by bootstrap.js.

        4) For settings, we have a click handler in settings.js
           that will close the dialog via overlays.close_active_modal.

        5) We assume that since this is a modal, you will
           only ever have one dialog active at any
           time.

        6) If a modal wants a loading spinner, it should pass loading_spinner: true.
           This will show a loading spinner when the yes button is clicked.
           The caller is responsible for calling hide_dialog_spinner()
           to hide the spinner in both success and error handlers.

        7) If a caller needs to run code after the modal body is added
           to DOM, it can do so by passing a post_render hook.
*/

export function hide_dialog_spinner() {
    $(".dialog_submit_button .loader").hide();
    $(".dialog_submit_button span").show();
    $(".dialog_submit_button").prop("disabled", false);
    $("#dialog_widget_modal .close-modal-btn").prop("disabled", false);
}

export function show_dialog_spinner() {
    const using_dark_theme = settings_data.using_dark_theme();
    loading.show_button_spinner($(".dialog_submit_button .loader"), using_dark_theme);
    $(".dialog_submit_button span").hide();
    $(".dialog_submit_button").prop("disabled", true);
    $("#dialog_widget_modal .close-modal-btn").prop("disabled", true);
}

export function launch(conf) {
    const mandatory_fields = [
        // The html_ fields should be safe HTML. If callers
        // interpolate user data into strings, they should use
        // templates.
        "html_heading",
        "html_body",
        "on_click",
        "parent",
    ];

    // Optional parameters:
    // * html_submit_button: Submit button text.
    // * close_on_submit: Whether to close modal on clicking submit.
    // * focus_submit_on_open: Whether to focus submit button on open.
    // * danger_submit_button: Whether to use danger button styling for submit button.
    // * help_link: A help link in the heading area.

    for (const f of mandatory_fields) {
        if (conf[f] === undefined) {
            blueslip.error("programmer omitted " + f);
        }
    }

    // Close any existing modals--on settings screens you can
    // have multiple buttons that need confirmation.
    if (overlays.is_modal_open()) {
        overlays.close_modal("#dialog_widget_modal");
    }

    const html_submit_button = conf.html_submit_button || $t_html({defaultMessage: "Save changes"});
    const html_dialog_heading = render_dialog_heading({
        heading_text: conf.html_heading,
        link: conf.help_link,
    });
    const html = render_dialog_widget({
        fade: conf.fade,
        html_submit_button,
        html_dialog_heading,
        html_body: conf.html_body,
        danger_submit_button: conf.danger_submit_button,
    });
    const dialog = $(html);
    conf.parent.append(dialog);

    if (conf.post_render !== undefined) {
        conf.post_render();
    }

    const submit_button = dialog.find(".dialog_submit_button");
    // Set up handlers.
    submit_button.on("click", () => {
        if (conf.loading_spinner) {
            show_dialog_spinner();
        } else if (conf.close_on_submit) {
            overlays.close_modal("#dialog_widget_modal");
        }
        $("#dialog_error").empty();
        conf.on_click();
    });

    dialog.on("hidden.bs.modal", () => {
        dialog.remove();
    });

    if (conf.focus_submit_on_open) {
        conf.parent.on("shown.bs.modal", () => {
            submit_button.trigger("focus");
        });
    }

    // Open the modal
    overlays.open_modal("#dialog_widget_modal");
}
<commit_msg>dialog_widget: Pass the event object to the `on_click` function.<commit_after>import $ from "jquery";

import render_dialog_widget from "../templates/dialog_widget.hbs";
import render_dialog_heading from "../templates/dialog_widget_heading.hbs";

import * as blueslip from "./blueslip";
import {$t_html} from "./i18n";
import * as loading from "./loading";
import * as overlays from "./overlays";
import * as settings_data from "./settings_data";

/*
    Look for dialog_widget in settings_users
    to see an example of how to use this widget.  It's
    pretty simple to use!

    Some things to note:

        1) We create DOM on the fly, and we remove
           the DOM once it's closed.

        2) We attach the DOM for the modal to conf.parent,
           and this temporary DOM location will influence
           how styles work.

        3) The cancel button is driven by bootstrap.js.

        4) For settings, we have a click handler in settings.js
           that will close the dialog via overlays.close_active_modal.

        5) We assume that since this is a modal, you will
           only ever have one dialog active at any
           time.

        6) If a modal wants a loading spinner, it should pass loading_spinner: true.
           This will show a loading spinner when the yes button is clicked.
           The caller is responsible for calling hide_dialog_spinner()
           to hide the spinner in both success and error handlers.

        7) If a caller needs to run code after the modal body is added
           to DOM, it can do so by passing a post_render hook.
*/

export function hide_dialog_spinner() {
    $(".dialog_submit_button .loader").hide();
    $(".dialog_submit_button span").show();
    $(".dialog_submit_button").prop("disabled", false);
    $("#dialog_widget_modal .close-modal-btn").prop("disabled", false);
}

export function show_dialog_spinner() {
    const using_dark_theme = settings_data.using_dark_theme();
    loading.show_button_spinner($(".dialog_submit_button .loader"), using_dark_theme);
    $(".dialog_submit_button span").hide();
    $(".dialog_submit_button").prop("disabled", true);
    $("#dialog_widget_modal .close-modal-btn").prop("disabled", true);
}

export function launch(conf) {
    const mandatory_fields = [
        // The html_ fields should be safe HTML. If callers
        // interpolate user data into strings, they should use
        // templates.
        "html_heading",
        "html_body",
        "on_click",
        "parent",
    ];

    // Optional parameters:
    // * html_submit_button: Submit button text.
    // * close_on_submit: Whether to close modal on clicking submit.
    // * focus_submit_on_open: Whether to focus submit button on open.
    // * danger_submit_button: Whether to use danger button styling for submit button.
    // * help_link: A help link in the heading area.

    for (const f of mandatory_fields) {
        if (conf[f] === undefined) {
            blueslip.error("programmer omitted " + f);
        }
    }

    // Close any existing modals--on settings screens you can
    // have multiple buttons that need confirmation.
    if (overlays.is_modal_open()) {
        overlays.close_modal("#dialog_widget_modal");
    }

    const html_submit_button = conf.html_submit_button || $t_html({defaultMessage: "Save changes"});
    const html_dialog_heading = render_dialog_heading({
        heading_text: conf.html_heading,
        link: conf.help_link,
    });
    const html = render_dialog_widget({
        fade: conf.fade,
        html_submit_button,
        html_dialog_heading,
        html_body: conf.html_body,
        danger_submit_button: conf.danger_submit_button,
    });
    const dialog = $(html);
    conf.parent.append(dialog);

    if (conf.post_render !== undefined) {
        conf.post_render();
    }

    const submit_button = dialog.find(".dialog_submit_button");
    // Set up handlers.
    submit_button.on("click", (e) => {
        if (conf.loading_spinner) {
            show_dialog_spinner();
        } else if (conf.close_on_submit) {
            overlays.close_modal("#dialog_widget_modal");
        }
        $("#dialog_error").empty();
        conf.on_click(e);
    });

    dialog.on("hidden.bs.modal", () => {
        dialog.remove();
    });

    if (conf.focus_submit_on_open) {
        conf.parent.on("shown.bs.modal", () => {
            submit_button.trigger("focus");
        });
    }

    // Open the modal
    overlays.open_modal("#dialog_widget_modal");
}
<|endoftext|>


<commit_before>var helper = {
    getFirstArrayElementIndexByPropertyValue : function(arrIn, prop, val){
        var i = -1,
            indexes = this.getIndexesByPropertyValue(arrIn, prop, val);

        if (indexes.length > 0) {
            i = indexes[0];
        }

        return i;
    },
    getIndexesByPropertyValue :  function(arrIn, prop, val){
        var len = arrIn.length,
            indexes = [];
        if (len!=0){
            for (var i=0;i<len;i++){
                var el = arrIn[i];
                if (el.hasOwnProperty(prop) && el[prop] === val){
                    indexes.push(i);
                }
            }
        }
        return indexes;
    }
}

if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = helper;
    }
}<commit_msg>added flattify<commit_after>var helper = {
    getFirstArrayElementIndexByPropertyValue : function(arrIn, prop, val){
        var i = -1,
            indexes = this.getIndexesByPropertyValue(arrIn, prop, val);

        if (indexes.length > 0) {
            i = indexes[0];
        }

        return i;
    },
    getIndexesByPropertyValue :  function(arrIn, prop, val){
        var len = arrIn.length,
            indexes = [];
        if (len!=0){
            for (var i=0;i<len;i++){
                var el = arrIn[i];
                if (el.hasOwnProperty(prop) && el[prop] === val){
                    indexes.push(i);
                }
            }
        }
        return indexes;
    },

    flattify : function (obj){
        var retobj = {}
        for (var key in obj) {
            if (obj.hasOwnProperty(key)){
                if (typeof key === "function"){
                    if (key.substring(0,2) == "get"){
                        retobj[key.substr(3)] = obj.key();
                    }
                } else {
                    retobj[key] = obj.key;
                }
            }
        }
        return retobj;
    }
}

if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = helper;
    }
}<|endoftext|>


<commit_before>define([
  'jquery',
  'backbone',
  'underscore',
  'app'
], function($, Backbone, _, App) {

    var BASE_URL = '/proxy';

    // From https://gist.github.com/1610397
    function nestCollection(model, attributeName, nestedCollection) {
        //setup nested references
        for (var i = 0; i < nestedCollection.length; i++) {
          model.attributes[attributeName][i] = nestedCollection.at(i).attributes;
        }
        //create empty arrays if none

        nestedCollection.bind('add', function (initiative) {
          if (!model.get(attributeName)) {
            model.attributes[attributeName] = [];
          }
          model.get(attributeName).push(initiative.attributes);
        });

        nestedCollection.bind('remove', function (initiative) {
          var updateObj = {};
          updateObj[attributeName] = _.without(model.get(attributeName), initiative.attributes);
          model.set(updateObj);
        });
        return nestedCollection;
    }

    // Depaginate results from url
    function depaginatedRequest(url) {
        var d = $.Deferred();
        var l = [];

        // "http://example.com/page?foo=bar&baz=bat" --> ["http://example.com/page", "foo=bar&baz=bat"]
        var parsed_url = url.split('?', 2);

        // "http://example.com/page"
        var base_url = parsed_url[0] || '';

        // ["foo=bar", "baz=bat"]
        var query_params = (parsed_url[1] || '').split('&');

        function constructURL(base, params) {
            return [base, params.join('&')].join('?');
        }

        function handlePage(data) {
            l = l.concat(data.values);

            /* FIXME */
            if(data.metadata.next_marker == null ) {
                d.resolve(l);
            } else {
                $.getJSON(constructURL(base_url, query_params.concat(['marker=' + data.metadata.next_marker])), handlePage);
            }
        }

        $.getJSON(constructURL(base_url, query_params), handlePage);
        return d.promise();
    }

    var Account = Backbone.Model.extend({
        url: function() {
            return BASE_URL + '/account';
        },
        initialize: function() {
            this.entities = nestCollection(this, 'entities', new AccountEntityCollection([], {account: this}));
        }
    });

    /* ENTITIES */
    var Entity = Backbone.Model.extend({
        url: function() {
            return BASE_URL + '/entities/' + this.id;
        },
        parse: function(response) {
            this.account = response.account;
            delete response.account;
            return response;
        },
        initialize: function() {
            this.checks = nestCollection(this, 'checks', new EntityCheckCollectionFactory(this));
            this.alarms = nestCollection(this, 'alarms', new EntityAlarmCollectionFactory(this));
        },
        getAccount: function() {
            return getAccount();
        }
    });

    var AccountEntityCollection = Backbone.Collection.extend({
        model: Entity,
        initialize: function(models, options) {
            this.account = options.account;
        },
        url: function() {
            return BASE_URL + '/entities';
        },
        parse: function(response) {
            _.each(response, function(entity) {
                entity.account = this.account;
            }, this);

            return response;
        },
        sync: function(method, model, options) {
            return depaginatedRequest(this.url()).then(options.success, options.error);
        }
    });

    /* ALARMS */
    var Alarm = Backbone.Model.extend({
        urlRoot: function() {
            return BASE_URL + '/entities/' + this.get('entity_id') + '/alarms/' + this.get('id');
        },
        save: function(attributes, options) {
            attributes = typeof attributes !== 'undefined' ? attributes : {};

            cleaned_attr = _.clone(attributes);
            delete cleaned_attr.entity_id;

            Backbone.Model.prototype.save.call(this, cleaned_attr, options);
        }
    });

    function EntityAlarmCollectionFactory(entity) {
        var C = Backbone.Collection.extend({
            model: Alarm,
            url: function() {
                return BASE_URL + '/entities/' + entity.get('id') + '/alarms/';
            },

            parse: function(response) {
                _.each(response, function(alarm) {
                    alarm.entity_id = entity.get('id');
                }, this);
                return response;
            },

            filterByCheck: function(check) {

                var alarms = this.reject(function (alarm) {
                    return (alarm.get('check_id') !== check.get('id'));
                });

                return alarms;
            },

            sync: function(method, model, options) {
                return depaginatedRequest(this.url()).then(options.success, options.error);
            }
        });
        return new C();
    }

    /* CHECKS */

    var Check = Backbone.Model.extend({
        urlRoot: function() {
            return BASE_URL + '/entities/' + this.get('entity_id') + '/checks/';
        },
        initialize: function() {
            this.metrics = nestCollection(this, 'metrics', new CheckMetricCollectionFactory(this));
        },
        save: function(attributes, options) {
            attributes = typeof attributes !== 'undefined' ? attributes : {};

            cleaned_attr = _.clone(attributes);
            delete cleaned_attr.entity_id;

            Backbone.Model.prototype.save.call(this, cleaned_attr, options);
        },
        getEntity: function() {
            return App.getInstance().account.entities.get(this.get('entity_id'));
        }
    });

    function EntityCheckCollectionFactory(entity) {
        var C = Backbone.Collection.extend({
            model: Check,
            url: function() {
                return BASE_URL + '/entities/' + entity.id + '/checks';
            },
            parse: function(response) {
                _.each(response, function(check) {
                    check.entity_id = entity.id;
                }, this);

                return response;
            },
            sync: function(method, model, options) {
                return depaginatedRequest(this.url()).then(options.success, options.error);
            }
        });
        return new C();
    }

    /* METRICS */

    var Metric = Backbone.Model.extend({
        urlRoot: function() {
            return BASE_URL + '/entities/' + this.get('entity_id') + '/checks/' + this.get('check_id') + '/metrics/' + this.get('name');
        },
        save: function(attributes, options) {
            attributes = typeof attributes !== 'undefined' ? attributes : {};

            cleaned_attr = _.clone(attributes);
            delete cleaned_attr.entity_id;
            delete cleaned_attr.check_id;

            Backbone.Model.prototype.save.call(this, cleaned_attr, options);
        },
        getEntity: function(){
            getAccount().entities.fetch();
            return ACCOUNT.entities.get(this.get('entity_id'));
        },
        getCheck: function(){
            this.getEntity().checks.fetch();
            return this.getEntity().checks.get(this.get('check_id'));
        },
        getData: function(start_time, end_time, points, options) {
            return depaginatedRequest(this.url() + '?from=' + start_time + '&to=' + end_time + '&points=' + points).then(options.success, options.error);

        }
    });

    function CheckMetricCollectionFactory(check) {
        var C = Backbone.Collection.extend({
            model: Metric,
            url: function() {
                return BASE_URL + '/entities/' + check.get('entity_id') + '/checks/' + check.id + '/metrics';
            },
            parse: function(response) {
                _.each(response, function(metric) {
                    metric.entity_id = check.get('entity_id');
                    metric.check_id = check.id;
                }, this);

                return response;
            },
            sync: function(method, model, options) {
                return depaginatedRequest(this.url()).then(options.success, options.error);
            }
        });
        return new C();
    }

    return {'Account': Account, 'Entity': Entity, 'Check': Check};
});
<commit_msg>Added Date object support for getData.  getData now returns data with timestamps converted to Date objects.  Also added getRecentData function<commit_after>define([
  'jquery',
  'backbone',
  'underscore',
  'app'
], function($, Backbone, _, App) {

    var BASE_URL = '/proxy';

    // From https://gist.github.com/1610397
    function nestCollection(model, attributeName, nestedCollection) {
        //setup nested references
        for (var i = 0; i < nestedCollection.length; i++) {
          model.attributes[attributeName][i] = nestedCollection.at(i).attributes;
        }
        //create empty arrays if none

        nestedCollection.bind('add', function (initiative) {
          if (!model.get(attributeName)) {
            model.attributes[attributeName] = [];
          }
          model.get(attributeName).push(initiative.attributes);
        });

        nestedCollection.bind('remove', function (initiative) {
          var updateObj = {};
          updateObj[attributeName] = _.without(model.get(attributeName), initiative.attributes);
          model.set(updateObj);
        });
        return nestedCollection;
    }

    // Depaginate results from url
    function depaginatedRequest(url) {
        var d = $.Deferred();
        var l = [];

        // "http://example.com/page?foo=bar&baz=bat" --> ["http://example.com/page", "foo=bar&baz=bat"]
        var parsed_url = url.split('?', 2);

        // "http://example.com/page"
        var base_url = parsed_url[0] || '';

        // ["foo=bar", "baz=bat"]
        var query_params = (parsed_url[1] || '').split('&');

        function constructURL(base, params) {
            return [base, params.join('&')].join('?');
        }

        function handlePage(data) {
            l = l.concat(data.values);

            /* FIXME */
            if(data.metadata.next_marker == null ) {
                d.resolve(l);
            } else {
                $.getJSON(constructURL(base_url, query_params.concat(['marker=' + data.metadata.next_marker])), handlePage);
            }
        }

        $.getJSON(constructURL(base_url, query_params), handlePage);
        return d.promise();
    }

    var Account = Backbone.Model.extend({
        url: function() {
            return BASE_URL + '/account';
        },
        initialize: function() {
            this.entities = nestCollection(this, 'entities', new AccountEntityCollection([], {account: this}));
        }
    });

    /* ENTITIES */
    var Entity = Backbone.Model.extend({
        url: function() {
            return BASE_URL + '/entities/' + this.id;
        },
        parse: function(response) {
            this.account = response.account;
            delete response.account;
            return response;
        },
        initialize: function() {
            this.checks = nestCollection(this, 'checks', new EntityCheckCollectionFactory(this));
            this.alarms = nestCollection(this, 'alarms', new EntityAlarmCollectionFactory(this));
        },
        getAccount: function() {
            return getAccount();
        }
    });

    var AccountEntityCollection = Backbone.Collection.extend({
        model: Entity,
        initialize: function(models, options) {
            this.account = options.account;
        },
        url: function() {
            return BASE_URL + '/entities';
        },
        parse: function(response) {
            _.each(response, function(entity) {
                entity.account = this.account;
            }, this);

            return response;
        },
        sync: function(method, model, options) {
            return depaginatedRequest(this.url()).then(options.success, options.error);
        }
    });

    /* ALARMS */
    var Alarm = Backbone.Model.extend({
        urlRoot: function() {
            return BASE_URL + '/entities/' + this.get('entity_id') + '/alarms/' + this.get('id');
        },
        save: function(attributes, options) {
            attributes = typeof attributes !== 'undefined' ? attributes : {};

            cleaned_attr = _.clone(attributes);
            delete cleaned_attr.entity_id;

            Backbone.Model.prototype.save.call(this, cleaned_attr, options);
        }
    });

    function EntityAlarmCollectionFactory(entity) {
        var C = Backbone.Collection.extend({
            model: Alarm,
            url: function() {
                return BASE_URL + '/entities/' + entity.get('id') + '/alarms/';
            },

            parse: function(response) {
                _.each(response, function(alarm) {
                    alarm.entity_id = entity.get('id');
                }, this);
                return response;
            },

            filterByCheck: function(check) {

                var alarms = this.reject(function (alarm) {
                    return (alarm.get('check_id') !== check.get('id'));
                });

                return alarms;
            },

            sync: function(method, model, options) {
                return depaginatedRequest(this.url()).then(options.success, options.error);
            }
        });
        return new C();
    }

    /* CHECKS */

    var Check = Backbone.Model.extend({
        urlRoot: function() {
            return BASE_URL + '/entities/' + this.get('entity_id') + '/checks/';
        },
        initialize: function() {
            this.metrics = nestCollection(this, 'metrics', new CheckMetricCollectionFactory(this));
        },
        save: function(attributes, options) {
            attributes = typeof attributes !== 'undefined' ? attributes : {};

            cleaned_attr = _.clone(attributes);
            delete cleaned_attr.entity_id;

            Backbone.Model.prototype.save.call(this, cleaned_attr, options);
        },
        getEntity: function() {
            return App.getInstance().account.entities.get(this.get('entity_id'));
        }
    });

    function EntityCheckCollectionFactory(entity) {
        var C = Backbone.Collection.extend({
            model: Check,
            url: function() {
                return BASE_URL + '/entities/' + entity.id + '/checks';
            },
            parse: function(response) {
                _.each(response, function(check) {
                    check.entity_id = entity.id;
                }, this);

                return response;
            },
            sync: function(method, model, options) {
                return depaginatedRequest(this.url()).then(options.success, options.error);
            }
        });
        return new C();
    }

    /* METRICS */

    var Metric = Backbone.Model.extend({
        urlRoot: function() {
            return BASE_URL + '/entities/' + this.get('entity_id') + '/checks/' + this.get('check_id') + '/metrics/' + this.get('name');
        },
        save: function(attributes, options) {
            attributes = typeof attributes !== 'undefined' ? attributes : {};

            cleaned_attr = _.clone(attributes);
            delete cleaned_attr.entity_id;
            delete cleaned_attr.check_id;

            Backbone.Model.prototype.save.call(this, cleaned_attr, options);
        },
        getEntity: function(){
            getAccount().entities.fetch();
            return ACCOUNT.entities.get(this.get('entity_id'));
        },
        getCheck: function(){
            this.getEntity().checks.fetch();
            return this.getEntity().checks.get(this.get('check_id'));
        },
        getData: function(start_time, end_time, points, options) {
            /* Returns data for this metric between start_time and end_time, either in raw or rolled-up form.

                Parameters:
                    start_time: Date object, or number of ms since UNIX epoch (see Date.getTime())
                    end_time: Date object, or number of ms since UNIX epoch (see Date.getTime())
                    points: Approximate number of points to be returned
                    options.success: Success callback
                    options.error: Error callback
            */
            if(start_time.getTime) {
                start_time = start_time.getTime();
            }
            if(end_time.getTime) {
                end_time = end_time.getTime();
            }

            function formatData(list) {
                _.each(list, function(d) {
                    d.timestamp = new Date(d.timestamp);
                }, this);
                return list;
            };
            return depaginatedRequest(this.url() + '?from=' + start_time + '&to=' + end_time + '&points=' + points).done(formatData).then(options.success, options.error);
        },
        getRecentData: function(period, points, options) {
            /* Returns most recent data of a length specified by period

                Parameters:
                    period: Length of data period in ms
                    points: Approximate number of points to be returned
                    options.success: Success callback
                    options.error: Error callback
            */
            var end_time = (new Date()).getTime();

            var start_time = end_time - period;

            return this.getData(start_time, end_time, options);
        }
    });

    function CheckMetricCollectionFactory(check) {
        var C = Backbone.Collection.extend({
            model: Metric,
            url: function() {
                return BASE_URL + '/entities/' + check.get('entity_id') + '/checks/' + check.id + '/metrics';
            },
            parse: function(response) {
                _.each(response, function(metric) {
                    metric.entity_id = check.get('entity_id');
                    metric.check_id = check.id;
                }, this);
                return response;
            },
            sync: function(method, model, options) {
                return depaginatedRequest(this.url()).then(options.success, options.error);
            }
        });
        return new C();
    }

    return {'Account': Account, 'Entity': Entity, 'Check': Check, 'Metric': Metric};
});
<|endoftext|>


<commit_before>/**
 * New Web Thing.
 *
 * Represents a new web thing that can potentially be saved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const API = require('./api');
const Utils = require('./utils');

let idCounter = -1;
const getNewWebThingId = function() {
  idCounter += 1;
  return idCounter;
};

class NewWebThing {
  /**
   * New Web Thing constructor.
   */
  constructor() {
    this.id = `web-thing-${getNewWebThingId()}`;
    this.originalId = this.id;
    this.url = null;
    this.iconData = null;
    this.description = null;
    this.container = document.getElementById('new-things');
    this.render();
    this.element = document.getElementById(
      `new-web-thing-${Utils.escapeHtmlForIdClass(this.originalId)}`);
    this.thingType = this.element.getElementsByClassName('new-thing-type')[0];
    this.customIcon =
      this.element.getElementsByClassName('new-thing-custom-icon')[0];
    this.customIconInput =
      this.element.getElementsByClassName('new-thing-custom-icon-input')[0];
    this.customIconLabel =
      this.element.getElementsByClassName('new-thing-custom-icon-label')[0];
    this.label =
      this.element.getElementsByClassName('new-web-thing-label')[0];
    this.originLabel =
      this.element.getElementsByClassName('new-web-thing-origin')[0];
    this.urlInput = this.element.getElementsByClassName('new-web-thing-url')[0];
    this.nameInput =
      this.element.getElementsByClassName('new-web-thing-name')[0];
    this.cancelButton =
      this.element.getElementsByClassName('new-web-thing-cancel-button')[0];
    this.submitButton =
      this.element.getElementsByClassName('new-web-thing-submit-button')[0];
    this.saveButton =
      this.element.getElementsByClassName('new-web-thing-save-button')[0];
    this.element.addEventListener('click', this.handleClick.bind(this));
    this.thingType.addEventListener('change', this.handleTypeChange.bind(this));
    this.customIconInput.addEventListener('change',
                                          this.handleIconUpload.bind(this));
  }

  /**
   * HTML view for New Web Thing.
   */
  view() {
    const id = Utils.escapeHtmlForIdClass(`new-thing-custom-icon-${this.id}`);
    return `

          <webthing-custom-icon class="new-thing-custom-icon hidden">
          </webthing-custom-icon>

          <input type="text" class="new-web-thing-url"
            placeholder="Enter web thing URL"></input>
          <input type="text" class="new-web-thing-name hidden"></input>

          <select class="new-thing-type hidden"></select>

          <input type="file" class="new-thing-custom-icon-input hidden"
            id="${id}" accept="image/jpeg,image/png,image/svg+xml">

            Choose icon...

          Web Thing

            <button class="new-web-thing-cancel-button text-button">
              Cancel
            </button>
            <button class="new-web-thing-submit-button text-button">
              Submit
            </button>
            <button class="new-web-thing-save-button text-button hidden">
              Save
            </button>

`;
  }

  /**
   * Render New Web Thing view and add to DOM.
   */
  render() {
    this.container.insertAdjacentHTML('beforeend', this.view());
  }

  handleTypeChange() {
    const capability =
      this.thingType.options[this.thingType.selectedIndex].value;

    this.customIconLabel.classList.add('hidden');
    this.customIcon.classList.add('hidden');

    let cls = '';
    switch (capability) {
      case 'OnOffSwitch':
        cls = 'on-off-switch';
        break;
      case 'MultiLevelSwitch':
        cls = 'multi-level-switch';
        break;
      case 'ColorControl':
        cls = 'color-control';
        break;
      case 'EnergyMonitor':
        cls = 'energy-monitor';
        break;
      case 'BinarySensor':
        cls = 'binary-sensor';
        break;
      case 'MultiLevelSensor':
        cls = 'multi-level-sensor';
        break;
      case 'SmartPlug':
        cls = 'smart-plug';
        break;
      case 'Light':
        cls = 'light';
        break;
      case 'DoorSensor':
        cls = 'door-sensor';
        break;
      case 'MotionSensor':
        cls = 'motion-sensor';
        break;
      case 'PushButton':
        cls = 'push-button';
        break;
      case 'Custom':
        this.customIconLabel.classList.remove('hidden');
        this.customIcon.classList.remove('hidden');
        break;
      default:
        break;
    }

    this.element.classList.remove(
      'on-off-switch',
      'multi-level-switch',
      'color-control',
      'energy-monitor',
      'binary-sensor',
      'multi-level-sensor',
      'smart-plug',
      'light',
      'door-sensor',
      'motion-sensor',
      'push-button'
    );

    if (cls) {
      this.element.classList.add(cls);
    }
  }

  handleIconUpload() {
    this.label.classList.add('hidden');

    if (this.customIconInput.files.length === 0) {
      return;
    }

    const file = this.customIconInput.files[0];
    if (!['image/jpeg', 'image/png', 'image/svg+xml'].includes(file.type)) {
      this.label.innerText = 'Invalid file.';
      this.label.classList.add('error');
      this.label.clsssList.remove('hidden');
      return;
    }

    const reader = new FileReader();
    reader.onloadend = (e) => {
      if (e.target.error) {
        console.error(e.target.error);
        this.label.innerText = 'Failed to read file.';
        this.label.classList.add('error');
        this.label.clsssList.remove('hidden');
        this.saveButton.disabled = false;
        return;
      }

      this.iconData = {
        mime: file.type,
        data: btoa(e.target.result),
      };
      this.saveButton.disabled = false;

      this.customIcon.iconHref = URL.createObjectURL(file);
      this.customIcon.classList.remove('hidden');
    };

    this.saveButton.disabled = true;
    reader.readAsBinaryString(file);
  }

  /**
   * Handle click on a Thing.
   */
  handleClick(event) {
    if (event.target.classList.contains('new-web-thing-save-button')) {
      this.save();
    } else if (event.target.classList.contains('new-web-thing-submit-button')) {
      this.submit();
    } else if (event.target.classList.contains('new-web-thing-cancel-button')) {
      this.cancel();
    }
  }

  cancel() {
    this.container.removeChild(this.element);
  }

  submit() {
    this.label.innerText = 'Loading...';
    this.label.classList.remove('error');

    // Clean up the provided URL
    let url = this.urlInput.value.replace(/\/$/, '');
    if (!url.startsWith('http://') && !url.startsWith('https://')) {
      url = `http://${url}`;
    }

    this.urlInput.value = url;
    this.url = url;

    let urlObj;
    try {
      urlObj = new URL(url);
    } catch (err) {
      return;
    }

    fetch('/new_things', {
      method: 'POST',
      body: JSON.stringify({url}),
      headers: {
        Authorization: `Bearer ${API.jwt}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    }).then((response) => {
      if (!response.ok) {
        return response.text();
      }

      return response.json();
    }).then((description) => {
      if (typeof description === 'string') {
        throw new Error(description);
      }

      // We don't support other gateways from this interface
      if (Array.isArray(description)) {
        this.label.innerText = 'Multiple web things found';
        this.label.classList.add('error');
        return;
      }

      let capabilities = [];
      if (Array.isArray(description['@type']) &&
          description['@type'].length > 0) {
        capabilities = description['@type'];
      } else if (description.type) {
        capabilities = Utils.legacyTypeToCapabilities(description.type);
      }

      capabilities = Utils.sortCapabilities(capabilities);
      capabilities.push('Custom');

      this.customIconLabel.classList.add('hidden');

      let cls = '';
      for (const capability of capabilities) {
        const option = document.createElement('option');
        option.value = capability;

        switch (capability) {
          case 'OnOffSwitch':
            option.innerText = 'On/Off Switch';
            cls = cls || 'on-off-switch';
            break;
          case 'MultiLevelSwitch':
            option.innerText = 'Multi Level Switch';
            cls = cls || 'multi-level-switch';
            break;
          case 'ColorControl':
            option.innerText = 'Color Control';
            cls = cls || 'color-control';
            break;
          case 'EnergyMonitor':
            option.innerText = 'Energy Monitor';
            cls = cls || 'energy-monitor';
            break;
          case 'BinarySensor':
            option.innerText = 'Binary Sensor';
            cls = cls || 'binary-sensor';
            break;
          case 'MultiLevelSensor':
            option.innerText = 'Multi Level Sensor';
            cls = cls || 'multi-level-sensor';
            break;
          case 'SmartPlug':
            option.innerText = 'Smart Plug';
            cls = cls || 'smart-plug';
            break;
          case 'Light':
            option.innerText = 'Light';
            cls = cls || 'light';
            break;
          case 'DoorSensor':
            option.innerText = 'Door Sensor';
            cls = cls || 'door-sensor';
            break;
          case 'MotionSensor':
            option.innerText = 'Motion Sensor';
            cls = cls || 'motion-sensor';
            break;
          case 'PushButton':
            option.innerText = 'Push Button';
            cls = cls || 'push-button';
            break;
          case 'Custom':
            option.innerText = 'Custom Thing';
            cls = cls || (capabilities.length > 1 ? '' : ' ');
            break;
          default:
            option.innerText = capability;
            cls = cls || (capabilities.length > 1 ? '' : ' ');
            break;
        }

        if (this.thingType.options.length === 0) {
          option.selected = true;

          if (capability === 'Custom') {
            this.customIconLabel.classList.remove('hidden');
          }
        }

        this.thingType.appendChild(option);
      }

      this.description = description;

      // If an href was included, use that for the URL instead.
      if (this.description.hasOwnProperty('href')) {
        this.url = urlObj.origin + description.href;
      }

      // Generate an ID. This must generate IDs identical to thing-url-adapter.
      this.id = this.url.replace(/[:/]/g, '-');

      this.element.classList.remove('web-thing');
      this.label.classList.remove('error');
      this.label.classList.add('hidden');
      this.thingType.classList.remove('hidden');
      this.nameInput.value = description.name;
      this.originLabel.innerText = `from ${urlObj.host}`;
      this.urlInput.classList.add('hidden');
      this.nameInput.classList.remove('hidden');
      this.submitButton.classList.add('hidden');
      this.saveButton.classList.remove('hidden');
      this.originLabel.classList.remove('hidden');

      cls = cls.trim();
      if (cls) {
        this.element.classList.add(cls);
      }
    }).catch((error) => {
      this.label.innerText = error.message;
      this.label.classList.add('error');
      console.error('Failed to check web thing:', error.message);
    });
  }

  save() {
    this.thingType.disabled = true;
    this.nameInput.disabled = true;
    this.saveButton.disabled = true;
    this.customIconInput.disabled = true;
    this.cancelButton.classList.add('hidden');

    const thing = this.description;
    thing.id = this.id;
    thing.name = this.nameInput.value;
    thing.webthingUrl = this.url;

    if (this.thingType.options.length > 0) {
      thing.selectedCapability =
        this.thingType.options[this.thingType.selectedIndex].value;
    }

    if (thing.selectedCapability === 'Custom' && this.iconData) {
      thing.iconData = this.iconData;
    }

    fetch('/things', {
      method: 'POST',
      body: JSON.stringify(thing),
      headers: {
        Authorization: `Bearer ${API.jwt}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    }).then((response) => {
      if (!response.ok) {
        return response.text();
      }

      return response.json();
    }).then((description) => {
      if (typeof description === 'string') {
        throw new Error(description);
      }

      this.saveButton.innerHTML = 'Saved';
    }).catch((error) => {
      console.error('Failed to save web thing:', error.message);
      this.label.innerText = 'Failed to save.';
      this.label.classList.add('error');
      this.label.classList.remove('hidden');
      this.thingType.disabled = false;
      this.nameInput.disabled = false;
      this.saveButton.disabled = false;
      this.customIconInput.disabled = false;
      this.cancelButton.classList.remove('hidden');
      this.reset();
    });
  }

  reset() {
    this.description = null;
    this.url = null;
    this.id = this.originalId;

    this.urlInput.value = '';
    this.nameInput.value = '';
    this.originLabel.innerText = '';
    this.element.classList.remove(
      'binary-sensor',
      'multi-level-sensor',
      'light',
      'on-off-switch',
      'multi-level-switch',
      'color-control',
      'energy-monitor',
      'smart-plug',
      'door-sensor',
      'motion-sensor',
      'push-button'
    );
    this.element.classList.add('web-thing');
    this.urlInput.classList.remove('hidden');
    this.nameInput.classList.add('hidden');
    this.submitButton.classList.remove('hidden');
    this.saveButton.classList.add('hidden');
    this.originLabel.classList.add('hidden');
  }
}

module.exports = NewWebThing;
<commit_msg>Change Cancel button to Done after saving web thing. (#1410)<commit_after>/**
 * New Web Thing.
 *
 * Represents a new web thing that can potentially be saved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const API = require('./api');
const Utils = require('./utils');

let idCounter = -1;
const getNewWebThingId = function() {
  idCounter += 1;
  return idCounter;
};

class NewWebThing {
  /**
   * New Web Thing constructor.
   */
  constructor() {
    this.id = `web-thing-${getNewWebThingId()}`;
    this.originalId = this.id;
    this.url = null;
    this.iconData = null;
    this.description = null;
    this.container = document.getElementById('new-things');
    this.render();
    this.element = document.getElementById(
      `new-web-thing-${Utils.escapeHtmlForIdClass(this.originalId)}`);
    this.thingType = this.element.getElementsByClassName('new-thing-type')[0];
    this.customIcon =
      this.element.getElementsByClassName('new-thing-custom-icon')[0];
    this.customIconInput =
      this.element.getElementsByClassName('new-thing-custom-icon-input')[0];
    this.customIconLabel =
      this.element.getElementsByClassName('new-thing-custom-icon-label')[0];
    this.label =
      this.element.getElementsByClassName('new-web-thing-label')[0];
    this.originLabel =
      this.element.getElementsByClassName('new-web-thing-origin')[0];
    this.urlInput = this.element.getElementsByClassName('new-web-thing-url')[0];
    this.nameInput =
      this.element.getElementsByClassName('new-web-thing-name')[0];
    this.cancelButton =
      this.element.getElementsByClassName('new-web-thing-cancel-button')[0];
    this.submitButton =
      this.element.getElementsByClassName('new-web-thing-submit-button')[0];
    this.saveButton =
      this.element.getElementsByClassName('new-web-thing-save-button')[0];
    this.element.addEventListener('click', this.handleClick.bind(this));
    this.thingType.addEventListener('change', this.handleTypeChange.bind(this));
    this.customIconInput.addEventListener('change',
                                          this.handleIconUpload.bind(this));
  }

  /**
   * HTML view for New Web Thing.
   */
  view() {
    const id = Utils.escapeHtmlForIdClass(`new-thing-custom-icon-${this.id}`);
    return `

          <webthing-custom-icon class="new-thing-custom-icon hidden">
          </webthing-custom-icon>

          <input type="text" class="new-web-thing-url"
            placeholder="Enter web thing URL"></input>
          <input type="text" class="new-web-thing-name hidden"></input>

          <select class="new-thing-type hidden"></select>

          <input type="file" class="new-thing-custom-icon-input hidden"
            id="${id}" accept="image/jpeg,image/png,image/svg+xml">

            Choose icon...

          Web Thing

            <button class="new-web-thing-cancel-button text-button">
              Cancel
            </button>
            <button class="new-web-thing-submit-button text-button">
              Submit
            </button>
            <button class="new-web-thing-save-button text-button hidden">
              Save
            </button>

`;
  }

  /**
   * Render New Web Thing view and add to DOM.
   */
  render() {
    this.container.insertAdjacentHTML('beforeend', this.view());
  }

  handleTypeChange() {
    const capability =
      this.thingType.options[this.thingType.selectedIndex].value;

    this.customIconLabel.classList.add('hidden');
    this.customIcon.classList.add('hidden');

    let cls = '';
    switch (capability) {
      case 'OnOffSwitch':
        cls = 'on-off-switch';
        break;
      case 'MultiLevelSwitch':
        cls = 'multi-level-switch';
        break;
      case 'ColorControl':
        cls = 'color-control';
        break;
      case 'EnergyMonitor':
        cls = 'energy-monitor';
        break;
      case 'BinarySensor':
        cls = 'binary-sensor';
        break;
      case 'MultiLevelSensor':
        cls = 'multi-level-sensor';
        break;
      case 'SmartPlug':
        cls = 'smart-plug';
        break;
      case 'Light':
        cls = 'light';
        break;
      case 'DoorSensor':
        cls = 'door-sensor';
        break;
      case 'MotionSensor':
        cls = 'motion-sensor';
        break;
      case 'PushButton':
        cls = 'push-button';
        break;
      case 'Custom':
        this.customIconLabel.classList.remove('hidden');
        this.customIcon.classList.remove('hidden');
        break;
      default:
        break;
    }

    this.element.classList.remove(
      'on-off-switch',
      'multi-level-switch',
      'color-control',
      'energy-monitor',
      'binary-sensor',
      'multi-level-sensor',
      'smart-plug',
      'light',
      'door-sensor',
      'motion-sensor',
      'push-button'
    );

    if (cls) {
      this.element.classList.add(cls);
    }
  }

  handleIconUpload() {
    this.label.classList.add('hidden');

    if (this.customIconInput.files.length === 0) {
      return;
    }

    const file = this.customIconInput.files[0];
    if (!['image/jpeg', 'image/png', 'image/svg+xml'].includes(file.type)) {
      this.label.innerText = 'Invalid file.';
      this.label.classList.add('error');
      this.label.clsssList.remove('hidden');
      return;
    }

    const reader = new FileReader();
    reader.onloadend = (e) => {
      if (e.target.error) {
        console.error(e.target.error);
        this.label.innerText = 'Failed to read file.';
        this.label.classList.add('error');
        this.label.clsssList.remove('hidden');
        this.saveButton.disabled = false;
        return;
      }

      this.iconData = {
        mime: file.type,
        data: btoa(e.target.result),
      };
      this.saveButton.disabled = false;

      this.customIcon.iconHref = URL.createObjectURL(file);
      this.customIcon.classList.remove('hidden');
    };

    this.saveButton.disabled = true;
    reader.readAsBinaryString(file);
  }

  /**
   * Handle click on a Thing.
   */
  handleClick(event) {
    if (event.target.classList.contains('new-web-thing-save-button')) {
      this.save();
    } else if (event.target.classList.contains('new-web-thing-submit-button')) {
      this.submit();
    } else if (event.target.classList.contains('new-web-thing-cancel-button')) {
      this.cancel();
    }
  }

  cancel() {
    this.container.removeChild(this.element);
  }

  submit() {
    this.label.innerText = 'Loading...';
    this.label.classList.remove('error');

    // Clean up the provided URL
    let url = this.urlInput.value.replace(/\/$/, '');
    if (!url.startsWith('http://') && !url.startsWith('https://')) {
      url = `http://${url}`;
    }

    this.urlInput.value = url;
    this.url = url;

    let urlObj;
    try {
      urlObj = new URL(url);
    } catch (err) {
      return;
    }

    fetch('/new_things', {
      method: 'POST',
      body: JSON.stringify({url}),
      headers: {
        Authorization: `Bearer ${API.jwt}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    }).then((response) => {
      if (!response.ok) {
        return response.text();
      }

      return response.json();
    }).then((description) => {
      if (typeof description === 'string') {
        throw new Error(description);
      }

      // We don't support other gateways from this interface
      if (Array.isArray(description)) {
        this.label.innerText = 'Multiple web things found';
        this.label.classList.add('error');
        return;
      }

      let capabilities = [];
      if (Array.isArray(description['@type']) &&
          description['@type'].length > 0) {
        capabilities = description['@type'];
      } else if (description.type) {
        capabilities = Utils.legacyTypeToCapabilities(description.type);
      }

      capabilities = Utils.sortCapabilities(capabilities);
      capabilities.push('Custom');

      this.customIconLabel.classList.add('hidden');

      let cls = '';
      for (const capability of capabilities) {
        const option = document.createElement('option');
        option.value = capability;

        switch (capability) {
          case 'OnOffSwitch':
            option.innerText = 'On/Off Switch';
            cls = cls || 'on-off-switch';
            break;
          case 'MultiLevelSwitch':
            option.innerText = 'Multi Level Switch';
            cls = cls || 'multi-level-switch';
            break;
          case 'ColorControl':
            option.innerText = 'Color Control';
            cls = cls || 'color-control';
            break;
          case 'EnergyMonitor':
            option.innerText = 'Energy Monitor';
            cls = cls || 'energy-monitor';
            break;
          case 'BinarySensor':
            option.innerText = 'Binary Sensor';
            cls = cls || 'binary-sensor';
            break;
          case 'MultiLevelSensor':
            option.innerText = 'Multi Level Sensor';
            cls = cls || 'multi-level-sensor';
            break;
          case 'SmartPlug':
            option.innerText = 'Smart Plug';
            cls = cls || 'smart-plug';
            break;
          case 'Light':
            option.innerText = 'Light';
            cls = cls || 'light';
            break;
          case 'DoorSensor':
            option.innerText = 'Door Sensor';
            cls = cls || 'door-sensor';
            break;
          case 'MotionSensor':
            option.innerText = 'Motion Sensor';
            cls = cls || 'motion-sensor';
            break;
          case 'PushButton':
            option.innerText = 'Push Button';
            cls = cls || 'push-button';
            break;
          case 'Custom':
            option.innerText = 'Custom Thing';
            cls = cls || (capabilities.length > 1 ? '' : ' ');
            break;
          default:
            option.innerText = capability;
            cls = cls || (capabilities.length > 1 ? '' : ' ');
            break;
        }

        if (this.thingType.options.length === 0) {
          option.selected = true;

          if (capability === 'Custom') {
            this.customIconLabel.classList.remove('hidden');
          }
        }

        this.thingType.appendChild(option);
      }

      this.description = description;

      // If an href was included, use that for the URL instead.
      if (this.description.hasOwnProperty('href')) {
        this.url = urlObj.origin + description.href;
      }

      // Generate an ID. This must generate IDs identical to thing-url-adapter.
      this.id = this.url.replace(/[:/]/g, '-');

      this.element.classList.remove('web-thing');
      this.label.classList.remove('error');
      this.label.classList.add('hidden');
      this.thingType.classList.remove('hidden');
      this.nameInput.value = description.name;
      this.originLabel.innerText = `from ${urlObj.host}`;
      this.urlInput.classList.add('hidden');
      this.nameInput.classList.remove('hidden');
      this.submitButton.classList.add('hidden');
      this.saveButton.classList.remove('hidden');
      this.originLabel.classList.remove('hidden');

      cls = cls.trim();
      if (cls) {
        this.element.classList.add(cls);
      }
    }).catch((error) => {
      this.label.innerText = error.message;
      this.label.classList.add('error');
      console.error('Failed to check web thing:', error.message);
    });
  }

  save() {
    this.thingType.disabled = true;
    this.nameInput.disabled = true;
    this.saveButton.disabled = true;
    this.customIconInput.disabled = true;
    this.cancelButton.classList.add('hidden');

    const thing = this.description;
    thing.id = this.id;
    thing.name = this.nameInput.value;
    thing.webthingUrl = this.url;

    if (this.thingType.options.length > 0) {
      thing.selectedCapability =
        this.thingType.options[this.thingType.selectedIndex].value;
    }

    if (thing.selectedCapability === 'Custom' && this.iconData) {
      thing.iconData = this.iconData;
    }

    fetch('/things', {
      method: 'POST',
      body: JSON.stringify(thing),
      headers: {
        Authorization: `Bearer ${API.jwt}`,
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
    }).then((response) => {
      if (!response.ok) {
        return response.text();
      }

      return response.json();
    }).then((description) => {
      if (typeof description === 'string') {
        throw new Error(description);
      }

      this.saveButton.innerHTML = 'Saved';

      const cancelButton = document.getElementById('add-thing-cancel-button');
      if (cancelButton) {
        cancelButton.textContent = 'Done';
      }
    }).catch((error) => {
      console.error('Failed to save web thing:', error.message);
      this.label.innerText = 'Failed to save.';
      this.label.classList.add('error');
      this.label.classList.remove('hidden');
      this.thingType.disabled = false;
      this.nameInput.disabled = false;
      this.saveButton.disabled = false;
      this.customIconInput.disabled = false;
      this.cancelButton.classList.remove('hidden');
      this.reset();
    });
  }

  reset() {
    this.description = null;
    this.url = null;
    this.id = this.originalId;

    this.urlInput.value = '';
    this.nameInput.value = '';
    this.originLabel.innerText = '';
    this.element.classList.remove(
      'binary-sensor',
      'multi-level-sensor',
      'light',
      'on-off-switch',
      'multi-level-switch',
      'color-control',
      'energy-monitor',
      'smart-plug',
      'door-sensor',
      'motion-sensor',
      'push-button'
    );
    this.element.classList.add('web-thing');
    this.urlInput.classList.remove('hidden');
    this.nameInput.classList.add('hidden');
    this.submitButton.classList.remove('hidden');
    this.saveButton.classList.add('hidden');
    this.originLabel.classList.add('hidden');
  }
}

module.exports = NewWebThing;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { observer } from 'mobx-react';
import injectSheet from 'react-jss';
import { Icon } from '@meetfranz/ui';
import { defineMessages, intlShape } from 'react-intl';

import {
  mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline, mdiEarth,
} from '@mdi/js';

const messages = defineMessages({
  goHome: {
    id: 'webControls.goHome',
    defaultMessage: '!!!Home',
  },
  openInBrowser: {
    id: 'webControls.openInBrowser',
    defaultMessage: '!!!Open in Browser',
  },
  back: {
    id: 'webControls.back',
    defaultMessage: '!!!Back',
  },
  forward: {
    id: 'webControls.forward',
    defaultMessage: '!!!Forward',
  },
  reload: {
    id: 'webControls.reload',
    defaultMessage: '!!!Reload',
  },
});

const styles = theme => ({
  root: {
    background: theme.colorBackground,
    position: 'relative',
    borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor],
    zIndex: 300,
    height: 50,
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
    padding: [0, 10],

    '& + div': {
      height: 'calc(100% - 50px)',
    },
  },
  button: {
    width: 30,
    height: 50,
    transition: 'opacity 0.25s',

    '&:hover': {
      opacity: 0.8,
    },

    '&:disabled': {
      opacity: 0.5,
    },
  },
  icon: {
    width: '20px !important',
    height: 20,
    marginTop: 5,
  },
  input: {
    marginBottom: 0,
    height: 'auto',
    margin: [0, 10],
    flex: 1,
    border: 0,
    padding: [4, 10],
    borderRadius: theme.borderRadius,
    background: theme.inputBackground,
    color: theme.inputColor,
  },
  inputButton: {
    color: theme.colorText,
  },
});

@injectSheet(styles) @observer
class WebControls extends Component {
  static propTypes = {
    classes: PropTypes.object.isRequired,
    goHome: PropTypes.func.isRequired,
    canGoBack: PropTypes.bool.isRequired,
    goBack: PropTypes.func.isRequired,
    canGoForward: PropTypes.bool.isRequired,
    goForward: PropTypes.func.isRequired,
    reload: PropTypes.func.isRequired,
    openInBrowser: PropTypes.func.isRequired,
    url: PropTypes.string.isRequired,
    navigate: PropTypes.func.isRequired,
  }

  static contextTypes = {
    intl: intlShape,
  };

  static getDerivedStateFromProps(props, state) {
    const { url } = props;
    const { editUrl } = state;

    if (!editUrl) {
      return {
        inputUrl: url,
        editUrl: state.editUrl,
      };
    }
  }

  inputRef = React.createRef();

  state = {
    inputUrl: '',
    editUrl: false,
  }

  render() {
    const {
      classes,
      goHome,
      canGoBack,
      goBack,
      canGoForward,
      goForward,
      reload,
      openInBrowser,
      url,
      navigate,
    } = this.props;

    const {
      inputUrl,
      editUrl,
    } = this.state;

    const { intl } = this.context;

    return (

        <button
          onClick={goHome}
          type="button"
          className={classes.button}
          data-tip={intl.formatMessage(messages.goHome)}
          data-place="bottom"
        >
          <Icon
            icon={mdiHomeOutline}
            className={classes.icon}
          />
        </button>
        <button
          onClick={goBack}
          type="button"
          className={classes.button}
          disabled={!canGoBack}
          data-tip={intl.formatMessage(messages.back)}
          data-place="bottom"
        >
          <Icon
            icon={mdiArrowLeft}
            className={classes.icon}
          />
        </button>
        <button
          onClick={goForward}
          type="button"
          className={classes.button}
          disabled={!canGoForward}
          data-tip={intl.formatMessage(messages.forward)}
          data-place="bottom"
        >
          <Icon
            icon={mdiArrowRight}
            className={classes.icon}
          />
        </button>
        <button
          onClick={reload}
          type="button"
          className={classes.button}
          data-tip={intl.formatMessage(messages.reload)}
          data-place="bottom"
        >
          <Icon
            icon={mdiReload}
            className={classes.icon}
          />
        </button>
        <input
          value={editUrl ? inputUrl : url}
          className={classes.input}
          onChange={event => this.setState({
            inputUrl: event.target.value,
          })}
          onFocus={(event) => {
            console.log('on focus event');
            event.target.select();
            this.setState({
              editUrl: true,
            });
          }}
          onKeyDown={(event) => {
            if (event.key === 'Enter') {
              this.setState({
                editUrl: false,
              });
              navigate(inputUrl);
              this.inputRef.current.blur();
            } else if (event.key === 'Escape') {
              this.setState({
                editUrl: false,
                inputUrl: url,
              });
              event.target.blur();
            }
          }}
          ref={this.inputRef}
        />
        <button
          onClick={openInBrowser}
          type="button"
          className={classes.button}
          data-tip={intl.formatMessage(messages.openInBrowser)}
          data-place="bottom"
        >
          <Icon
            icon={mdiEarth}
            className={classes.icon}
          />
        </button>

    );
  }
}

export default WebControls;
<commit_msg>fix(Custom Website): Fix browser control icons in dark mode<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { observer } from 'mobx-react';
import injectSheet from 'react-jss';
import { Icon } from '@meetfranz/ui';
import { defineMessages, intlShape } from 'react-intl';

import {
  mdiReload, mdiArrowRight, mdiArrowLeft, mdiHomeOutline, mdiEarth,
} from '@mdi/js';

const messages = defineMessages({
  goHome: {
    id: 'webControls.goHome',
    defaultMessage: '!!!Home',
  },
  openInBrowser: {
    id: 'webControls.openInBrowser',
    defaultMessage: '!!!Open in Browser',
  },
  back: {
    id: 'webControls.back',
    defaultMessage: '!!!Back',
  },
  forward: {
    id: 'webControls.forward',
    defaultMessage: '!!!Forward',
  },
  reload: {
    id: 'webControls.reload',
    defaultMessage: '!!!Reload',
  },
});

const styles = theme => ({
  root: {
    background: theme.colorBackground,
    position: 'relative',
    borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor],
    zIndex: 300,
    height: 50,
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
    padding: [0, 10],

    '& + div': {
      height: 'calc(100% - 50px)',
    },
  },
  button: {
    width: 30,
    height: 50,
    transition: 'opacity 0.25s',

    '&:hover': {
      opacity: 0.8,
    },

    '&:disabled': {
      opacity: 0.5,
    },
  },
  icon: {
    width: '20px !important',
    height: 20,
    marginTop: 5,
    color: theme.colorText,
  },
  input: {
    marginBottom: 0,
    height: 'auto',
    margin: [0, 10],
    flex: 1,
    border: 0,
    padding: [4, 10],
    borderRadius: theme.borderRadius,
    background: theme.inputBackground,
    color: theme.inputColor,
  },
  inputButton: {
    color: theme.colorText,
  },
});

@injectSheet(styles) @observer
class WebControls extends Component {
  static propTypes = {
    classes: PropTypes.object.isRequired,
    goHome: PropTypes.func.isRequired,
    canGoBack: PropTypes.bool.isRequired,
    goBack: PropTypes.func.isRequired,
    canGoForward: PropTypes.bool.isRequired,
    goForward: PropTypes.func.isRequired,
    reload: PropTypes.func.isRequired,
    openInBrowser: PropTypes.func.isRequired,
    url: PropTypes.string.isRequired,
    navigate: PropTypes.func.isRequired,
  }

  static contextTypes = {
    intl: intlShape,
  };

  static getDerivedStateFromProps(props, state) {
    const { url } = props;
    const { editUrl } = state;

    if (!editUrl) {
      return {
        inputUrl: url,
        editUrl: state.editUrl,
      };
    }
  }

  inputRef = React.createRef();

  state = {
    inputUrl: '',
    editUrl: false,
  }

  render() {
    const {
      classes,
      goHome,
      canGoBack,
      goBack,
      canGoForward,
      goForward,
      reload,
      openInBrowser,
      url,
      navigate,
    } = this.props;

    const {
      inputUrl,
      editUrl,
    } = this.state;

    const { intl } = this.context;

    return (

        <button
          onClick={goHome}
          type="button"
          className={classes.button}
          data-tip={intl.formatMessage(messages.goHome)}
          data-place="bottom"
        >
          <Icon
            icon={mdiHomeOutline}
            className={classes.icon}
          />
        </button>
        <button
          onClick={goBack}
          type="button"
          className={classes.button}
          disabled={!canGoBack}
          data-tip={intl.formatMessage(messages.back)}
          data-place="bottom"
        >
          <Icon
            icon={mdiArrowLeft}
            className={classes.icon}
          />
        </button>
        <button
          onClick={goForward}
          type="button"
          className={classes.button}
          disabled={!canGoForward}
          data-tip={intl.formatMessage(messages.forward)}
          data-place="bottom"
        >
          <Icon
            icon={mdiArrowRight}
            className={classes.icon}
          />
        </button>
        <button
          onClick={reload}
          type="button"
          className={classes.button}
          data-tip={intl.formatMessage(messages.reload)}
          data-place="bottom"
        >
          <Icon
            icon={mdiReload}
            className={classes.icon}
          />
        </button>
        <input
          value={editUrl ? inputUrl : url}
          className={classes.input}
          onChange={event => this.setState({
            inputUrl: event.target.value,
          })}
          onFocus={(event) => {
            console.log('on focus event');
            event.target.select();
            this.setState({
              editUrl: true,
            });
          }}
          onKeyDown={(event) => {
            if (event.key === 'Enter') {
              this.setState({
                editUrl: false,
              });
              navigate(inputUrl);
              this.inputRef.current.blur();
            } else if (event.key === 'Escape') {
              this.setState({
                editUrl: false,
                inputUrl: url,
              });
              event.target.blur();
            }
          }}
          ref={this.inputRef}
        />
        <button
          onClick={openInBrowser}
          type="button"
          className={classes.button}
          data-tip={intl.formatMessage(messages.openInBrowser)}
          data-place="bottom"
        >
          <Icon
            icon={mdiEarth}
            className={classes.icon}
          />
        </button>

    );
  }
}

export default WebControls;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'ServiceProviderAwareSupport',

  documentation: `Support methods for ServiceProviderAware DAOs and Sinks.
Use: see ServiceProviderAwareTest
        DAO dao = (DAO) new ServiceProviderAwareDAO.Builder(x)
                            .setDelegate(delegate)
                            .setPropertyInfos(
                              new HashMap<String, PropertyInfo[]>() {{
                                put(NotificationSetting.class.getName(), new PropertyInfo[] { NotificationSetting.OWNER });
                              }}
                            )
                            .build();

`,

  // TODO: ServiceProviderAwareMLang(property) performs EQ on getSpid on both objs.
  // and add to generation where 'findFoo' is available to avoid reflection.,

  javaImports: [
    'foam.core.FObject',
    'foam.core.PropertyInfo',
    'foam.core.X',
    'foam.nanos.auth.AuthService',
    'foam.nanos.auth.AuthorizationException',
    'foam.nanos.auth.Authorizer',
    'foam.nanos.auth.Subject',
    'foam.nanos.auth.User',
    'foam.nanos.logger.Logger',
    'foam.util.SafetyUtil',

    'java.lang.reflect.Method',
    'java.util.HashMap',
    'java.util.Map',
    'java.util.Map.Entry'
  ],

  properties: [
    {
      name: 'findMethods',
      class: 'Map',
      visibility: 'HIDDEN',
      javaFactory: 'return new HashMap();'
    },
    {
      name: 'propertyInfos',
      class: 'Map',
      visibility: 'HIDDEN',
      javaFactory: 'return new HashMap();'
    },
    {
      class: 'String',
      name: 'spid',
      documentation: 'The spid to be matched against. If not set, the context user spid will be used.'
    }
  ],

  methods: [
    {
      documentation: `Using Relationship findFoo(x), traverse relationships,
returning true if the spid or context users spid matches the current object.`,
      name: 'match',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'properties',
          type: 'Map'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'Boolean',
      javaCode: `
      var isUserSpid = false;
      var spid = getSpid();

      if ( SafetyUtil.isEmpty(spid) ) {
        var user = ((Subject) x.get("subject")).getUser();
        if ( user == null ) {
          // TODO/REVIEW: occurs during login. See AuthenticationApiTest
          return true;
        }
        spid = user.getSpid();
        isUserSpid = true;
      }

      var auth = (AuthService) x.get("auth");

      if ( obj != null &&
           obj instanceof ServiceProviderAware ) {
        ServiceProviderAware sp = (ServiceProviderAware) obj;
        return sp.getSpid().startsWith(spid) ||
                 isUserSpid && auth.check(x, getSpidReadPermission(sp.getSpid()));
      }

      Object result = obj;
      while ( result != null &&
              properties != null ) {
        PropertyInfo pInfos[] = (PropertyInfo[]) getProperties(x, properties, result);
        if ( pInfos == null ) {
          return false;
        }
        for ( int i = 0; i < pInfos.length; i++ ) {
          foam.core.PropertyInfo pInfo = pInfos[i];
          try {
            Method method = (Method) getFindMethods().get(pInfo.getName());
            if ( method == null ) {
              method = getFindMethod(x, pInfo.getName(), result);
            }
            result = invokeMethod(x, method, result);
            if ( result != null &&
                 result instanceof ServiceProviderAware ) {
              ServiceProviderAware sp = (ServiceProviderAware) result;
              return sp.getSpid().startsWith(spid) ||
                       isUserSpid && auth.check(x, getSpidReadPermission(sp.getSpid()));
            } else {
              break;
            }
          } catch (NoSuchMethodException e) {
            ((Logger) x.get("logger")).warning("ServiceProviderAwareSupport.match", result.getClass().getSimpleName(), pInfo.getName(), "NoSuchMethodException");
            return false;
          }
        }
      }
      return false;
     `
    },
    {
      documentation: `Search for obj class name in property info map.
If not found, test if argument obj is assignable from map class, and
store the result for subsequent lookups. `,
      name: 'getProperties',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'properties',
          type: 'Map'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'foam.core.PropertyInfo[]',
      javaCode: `
      String name = obj.getClass().getName();
      PropertyInfo[] pInfos = (PropertyInfo[]) getPropertyInfos().get(name);
      if ( pInfos != null ) {
        return pInfos;
      }

      for ( Object o : properties.entrySet() ) {
        Map.Entry entry = (Map.Entry) o;
        String key = entry.getKey().toString();
        try {
          Class cls = Class.forName(key);
          if ( cls.isAssignableFrom(obj.getClass()) ) {
           getPropertyInfos().put(name, entry.getValue());
            return (PropertyInfo[]) entry.getValue();
          }
        } catch ( ClassNotFoundException e ) {
          getPropertyInfos().put(name, new PropertyInfo[0]);
          break;
        }
      }
      return null;
     `
    },
    {
      name: 'getFindMethod',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'name',
          type: 'String'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'java.lang.reflect.Method',
      javaThrows: ['NoSuchMethodException'],
      javaCode: `
    Method method = (Method) getFindMethods().get(name);
    if ( method == null ) {
      String methodName = "find" + name.substring(0,1).toUpperCase() + name.substring(1);
      method = obj.getClass().getMethod(methodName, X.class);
      getFindMethods().put(name, method);
    }
    return method;
     `
    },
    {
      name: 'invokeMethod',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'method',
          type: 'java.lang.reflect.Method'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'Object',
      javaCode: `
        try {
          return method.invoke(obj, x);
        } catch (Throwable e) {
          Throwable cause = e.getCause();
          while ( cause.getCause() != null ) {
            cause = cause.getCause();
          }
          if ( cause != null &&
               cause instanceof AuthorizationException ) {
            ((Logger) x.get("logger")).debug("ServiceProviderAwareSupport.invokeMethod", obj.getClass().getSimpleName(), method.getName(), "AuthorizationException", cause.getMessage(), cause);
            return null;
          } else {
            ((Logger) x.get("logger")).error("ServiceProviderAwareSupport.invokeMethod", obj.getClass().getSimpleName(), method.getName(), e.getMessage(), e);
          }
        }
        return null;
      `
    },
    {
      name: 'getSpidReadPermission',
      type: 'String',
      args: [
        { name: 'spid', type: 'String' }
      ],
      javaCode: `
        return "serviceprovider.read." + (SafetyUtil.isEmpty(spid) ? "*" : spid);
      `
    }
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(`
          public ServiceProviderAwareSupport(String spid) {
            setSpid(spid);
          }
        `);
      }
    }
  ]
});
<commit_msg>Allow object with "*" spid to be found<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.auth',
  name: 'ServiceProviderAwareSupport',

  documentation: `Support methods for ServiceProviderAware DAOs and Sinks.
Use: see ServiceProviderAwareTest
        DAO dao = (DAO) new ServiceProviderAwareDAO.Builder(x)
                            .setDelegate(delegate)
                            .setPropertyInfos(
                              new HashMap<String, PropertyInfo[]>() {{
                                put(NotificationSetting.class.getName(), new PropertyInfo[] { NotificationSetting.OWNER });
                              }}
                            )
                            .build();

`,

  // TODO: ServiceProviderAwareMLang(property) performs EQ on getSpid on both objs.
  // and add to generation where 'findFoo' is available to avoid reflection.,

  javaImports: [
    'foam.core.FObject',
    'foam.core.PropertyInfo',
    'foam.core.X',
    'foam.nanos.auth.AuthService',
    'foam.nanos.auth.AuthorizationException',
    'foam.nanos.auth.Authorizer',
    'foam.nanos.auth.Subject',
    'foam.nanos.auth.User',
    'foam.nanos.logger.Logger',
    'foam.util.SafetyUtil',

    'java.lang.reflect.Method',
    'java.util.HashMap',
    'java.util.Map',
    'java.util.Map.Entry'
  ],

  properties: [
    {
      name: 'findMethods',
      class: 'Map',
      visibility: 'HIDDEN',
      javaFactory: 'return new HashMap();'
    },
    {
      name: 'propertyInfos',
      class: 'Map',
      visibility: 'HIDDEN',
      javaFactory: 'return new HashMap();'
    },
    {
      class: 'String',
      name: 'spid',
      documentation: 'The spid to be matched against. If not set, the context user spid will be used.'
    }
  ],

  methods: [
    {
      documentation: `Using Relationship findFoo(x), traverse relationships,
returning true if the spid or context users spid matches the current object.`,
      name: 'match',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'properties',
          type: 'Map'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'Boolean',
      javaCode: `
      var isUserSpid = false;
      var spid = getSpid();

      if ( SafetyUtil.isEmpty(spid) ) {
        var user = ((Subject) x.get("subject")).getUser();
        if ( user == null ) {
          // TODO/REVIEW: occurs during login. See AuthenticationApiTest
          return true;
        }
        spid = user.getSpid();
        isUserSpid = true;
      }

      var auth = (AuthService) x.get("auth");

      if ( obj != null &&
           obj instanceof ServiceProviderAware ) {
        ServiceProviderAware sp = (ServiceProviderAware) obj;
        return sp.getSpid().equals("*") || sp.getSpid().startsWith(spid) ||
                 isUserSpid && auth.check(x, getSpidReadPermission(sp.getSpid()));
      }

      Object result = obj;
      while ( result != null &&
              properties != null ) {
        PropertyInfo pInfos[] = (PropertyInfo[]) getProperties(x, properties, result);
        if ( pInfos == null ) {
          return false;
        }
        for ( int i = 0; i < pInfos.length; i++ ) {
          foam.core.PropertyInfo pInfo = pInfos[i];
          try {
            Method method = (Method) getFindMethods().get(pInfo.getName());
            if ( method == null ) {
              method = getFindMethod(x, pInfo.getName(), result);
            }
            result = invokeMethod(x, method, result);
            if ( result != null &&
                 result instanceof ServiceProviderAware ) {
              ServiceProviderAware sp = (ServiceProviderAware) result;
              return sp.getSpid().equals("*") || sp.getSpid().startsWith(spid) ||
                       isUserSpid && auth.check(x, getSpidReadPermission(sp.getSpid()));
            } else {
              break;
            }
          } catch (NoSuchMethodException e) {
            ((Logger) x.get("logger")).warning("ServiceProviderAwareSupport.match", result.getClass().getSimpleName(), pInfo.getName(), "NoSuchMethodException");
            return false;
          }
        }
      }
      return false;
     `
    },
    {
      documentation: `Search for obj class name in property info map.
If not found, test if argument obj is assignable from map class, and
store the result for subsequent lookups. `,
      name: 'getProperties',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'properties',
          type: 'Map'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'foam.core.PropertyInfo[]',
      javaCode: `
      String name = obj.getClass().getName();
      PropertyInfo[] pInfos = (PropertyInfo[]) getPropertyInfos().get(name);
      if ( pInfos != null ) {
        return pInfos;
      }

      for ( Object o : properties.entrySet() ) {
        Map.Entry entry = (Map.Entry) o;
        String key = entry.getKey().toString();
        try {
          Class cls = Class.forName(key);
          if ( cls.isAssignableFrom(obj.getClass()) ) {
           getPropertyInfos().put(name, entry.getValue());
            return (PropertyInfo[]) entry.getValue();
          }
        } catch ( ClassNotFoundException e ) {
          getPropertyInfos().put(name, new PropertyInfo[0]);
          break;
        }
      }
      return null;
     `
    },
    {
      name: 'getFindMethod',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'name',
          type: 'String'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'java.lang.reflect.Method',
      javaThrows: ['NoSuchMethodException'],
      javaCode: `
    Method method = (Method) getFindMethods().get(name);
    if ( method == null ) {
      String methodName = "find" + name.substring(0,1).toUpperCase() + name.substring(1);
      method = obj.getClass().getMethod(methodName, X.class);
      getFindMethods().put(name, method);
    }
    return method;
     `
    },
    {
      name: 'invokeMethod',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'method',
          type: 'java.lang.reflect.Method'
        },
        {
          name: 'obj',
          type: 'Object'
        }
      ],
      type: 'Object',
      javaCode: `
        try {
          return method.invoke(obj, x);
        } catch (Throwable e) {
          Throwable cause = e.getCause();
          while ( cause.getCause() != null ) {
            cause = cause.getCause();
          }
          if ( cause != null &&
               cause instanceof AuthorizationException ) {
            ((Logger) x.get("logger")).debug("ServiceProviderAwareSupport.invokeMethod", obj.getClass().getSimpleName(), method.getName(), "AuthorizationException", cause.getMessage(), cause);
            return null;
          } else {
            ((Logger) x.get("logger")).error("ServiceProviderAwareSupport.invokeMethod", obj.getClass().getSimpleName(), method.getName(), e.getMessage(), e);
          }
        }
        return null;
      `
    },
    {
      name: 'getSpidReadPermission',
      type: 'String',
      args: [
        { name: 'spid', type: 'String' }
      ],
      javaCode: `
        return "serviceprovider.read." + (SafetyUtil.isEmpty(spid) ? "*" : spid);
      `
    }
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(`
          public ServiceProviderAwareSupport(String spid) {
            setSpid(spid);
          }
        `);
      }
    }
  ]
});
<|endoftext|>


<commit_before>import esriConfig from "@arcgis/core/config.js";

import FeatureLayer from "@arcgis/core/layers/FeatureLayer";

import WebMap from "@arcgis/core/WebMap";
import MapView from "@arcgis/core/views/MapView";

import DotDensityRenderer from "@arcgis/core/renderers/DotDensityRenderer";

import Expand from "@arcgis/core/widgets/Expand";
import Bookmarks from "@arcgis/core/widgets/Bookmarks";
import Legend from "@arcgis/core/widgets/Legend";

// Required: Set this property to insure assets resolve correctly.
esriConfig.assetsPath = "./assets";

const map = new WebMap({
  portalItem: {
    id: "56b5bd522c52409c90d902285732e9f1",
  },
});

const view = new MapView({
  container: "viewDiv",
  map: map,
  highlightOptions: {
    fillOpacity: 0,
    color: [50, 50, 50],
  },
  popup: {
    dockEnabled: true,
    dockOptions: {
      position: "top-right",
      breakpoint: false,
    },
  },
  constraints: {
    maxScale: 35000,
  },
});

view.when().then(function () {
  const dotDensityRenderer = new DotDensityRenderer({
    dotValue: 100,
    outline: null,
    referenceScale: 577790, // 1:577,790 view scale
    legendOptions: {
      unit: "people",
    },
    attributes: [
      {
        field: "B03002_003E",
        color: "#f23c3f",
        label: "White (non-Hispanic)",
      },
      {
        field: "B03002_012E",
        color: "#e8ca0d",
        label: "Hispanic",
      },
      {
        field: "B03002_004E",
        color: "#00b6f1",
        label: "Black or African American",
      },
      {
        field: "B03002_006E",
        color: "#32ef94",
        label: "Asian",
      },
      {
        field: "B03002_005E",
        color: "#ff7fe9",
        label: "American Indian/Alaskan Native",
      },
      {
        field: "B03002_007E",
        color: "#e2c4a5",
        label: "Pacific Islander/Hawaiian Native",
      },
      {
        field: "B03002_008E",
        color: "#ff6a00",
        label: "Other race",
      },
      {
        field: "B03002_009E",
        color: "#96f7ef",
        label: "Two or more races",
      },
    ],
  });

  // Add renderer to the layer and define a popup template
  const url =
    "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/ACS_Population_by_Race_and_Hispanic_Origin_Boundaries/FeatureServer/2";
  const layer = new FeatureLayer({
    url: url,
    minScale: 20000000,
    maxScale: 35000,
    title: "Current Population Estimates (ACS)",
    popupTemplate: {
      title: "{County}, {State}",
      content: [
        {
          type: "fields",
          fieldInfos: [
            {
              fieldName: "B03002_003E",
              label: "White (non-Hispanic)",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
            {
              fieldName: "B03002_012E",
              label: "Hispanic",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
            {
              fieldName: "B03002_004E",
              label: "Black or African American",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
            {
              fieldName: "B03002_006E",
              label: "Asian",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
            {
              fieldName: "B03002_005E",
              label: "American Indian/Alaskan Native",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
            {
              fieldName: "B03002_007E",
              label: "Pacific Islander/Hawaiian Native",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
            {
              fieldName: "B03002_008E",
              label: "Other race",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
            {
              fieldName: "B03002_009E",
              label: "Two or more races",
              format: {
                digitSeparator: true,
                places: 0,
              },
            },
          ],
        },
      ],
    },
    renderer: dotDensityRenderer,
  });

  map.add(layer);

  view.ui.add(
    [
      new Expand({
        view: view,
        content: new Legend({ view: view }),
        group: "top-left",
        expanded: true,
      }),
      new Expand({
        view: view,
        content: new Bookmarks({ view: view }),
        group: "top-left",
      }),
    ],
    "top-left"
  );
});
<commit_msg>Webpack sample - update and refactoring<commit_after>import Bookmarks from "@arcgis/core/widgets/Bookmarks";
import DotDensityRenderer from "@arcgis/core/renderers/DotDensityRenderer";
import Expand from "@arcgis/core/widgets/Expand";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import Legend from "@arcgis/core/widgets/Legend";
import MapView from "@arcgis/core/views/MapView";
import WebMap from "@arcgis/core/WebMap";

const map = new WebMap({
  portalItem: {
    id: "56b5bd522c52409c90d902285732e9f1"
  }
});

const view = new MapView({
  container: "viewDiv",
  map: map,
  highlightOptions: {
    fillOpacity: 0,
    color: [50, 50, 50]
  },
  popup: {
    dockEnabled: true,
    dockOptions: {
      position: "top-right",
      breakpoint: false
    }
  },
  constraints: {
    maxScale: 35000
  }
});

view.when().then(function () {
  const dotDensityRenderer = new DotDensityRenderer({
    dotValue: 100,
    outline: null,
    referenceScale: 577790, // 1:577,790 view scale
    legendOptions: {
      unit: "people"
    },
    attributes: [
      {
        field: "B03002_003E",
        color: "#f23c3f",
        label: "White (non-Hispanic)"
      },
      {
        field: "B03002_012E",
        color: "#e8ca0d",
        label: "Hispanic"
      },
      {
        field: "B03002_004E",
        color: "#00b6f1",
        label: "Black or African American"
      },
      {
        field: "B03002_006E",
        color: "#32ef94",
        label: "Asian"
      },
      {
        field: "B03002_005E",
        color: "#ff7fe9",
        label: "American Indian/Alaskan Native"
      },
      {
        field: "B03002_007E",
        color: "#e2c4a5",
        label: "Pacific Islander/Hawaiian Native"
      },
      {
        field: "B03002_008E",
        color: "#ff6a00",
        label: "Other race"
      },
      {
        field: "B03002_009E",
        color: "#96f7ef",
        label: "Two or more races"
      }
    ]
  });

  // Add renderer to the layer and define a popup template
  const url =
    "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/ACS_Population_by_Race_and_Hispanic_Origin_Boundaries/FeatureServer/2";
  const layer = new FeatureLayer({
    url: url,
    minScale: 20000000,
    maxScale: 35000,
    title: "Current Population Estimates (ACS)",
    popupTemplate: {
      title: "{County}, {State}",
      content: [
        {
          type: "fields",
          fieldInfos: [
            {
              fieldName: "B03002_003E",
              label: "White (non-Hispanic)",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "B03002_012E",
              label: "Hispanic",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "B03002_004E",
              label: "Black or African American",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "B03002_006E",
              label: "Asian",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "B03002_005E",
              label: "American Indian/Alaskan Native",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "B03002_007E",
              label: "Pacific Islander/Hawaiian Native",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "B03002_008E",
              label: "Other race",
              format: {
                digitSeparator: true,
                places: 0
              }
            },
            {
              fieldName: "B03002_009E",
              label: "Two or more races",
              format: {
                digitSeparator: true,
                places: 0
              }
            }
          ]
        }
      ]
    },
    renderer: dotDensityRenderer
  });

  map.add(layer);

  view.ui.add(
    [
      new Expand({
        view: view,
        content: new Legend({ view: view }),
        group: "top-left",
        expanded: true
      }),
      new Expand({
        view: view,
        content: new Bookmarks({ view: view }),
        group: "top-left"
      })
    ],
    "top-left"
  );
});
<|endoftext|>