<commit_before>"use strict";

// Main JavaScript file for the integrations development panel at
// /devtools/integrations.

// Data Segment: We lazy load the requested fixtures from the backend
// as and when required and then cache them here.

const loaded_fixtures = new Map();
const url_base = "/api/v1/external/";

// A map defining how to clear the various UI elements.
const clear_handlers = {
    stream_name: "#stream_name",
    topic_name: "#topic_name",
    URL: "#URL",
    results_notice: "#results_notice",
    bot_name() {
        $("#bot_name").children()[0].selected = true;
    },
    integration_name() {
        $("#integration_name").children()[0].selected = true;
    },
    fixture_name() {
        $("#fixture_name").empty();
    },
    fixture_body() {
        $("#fixture_body")[0].value = "";
    },
    custom_http_headers() {
        $("#custom_http_headers")[0].value = "{}";
    },
    results() {
        $("#idp-results")[0].value = "";
    },
};

function clear_elements(elements) {
    // Supports strings (a selector to clear) or calling a function
    // (for more complex logic).
    for (const element_name of elements) {
        const handler = clear_handlers[element_name];
        if (typeof handler === "string") {
            const element_object = $(handler)[0];
            element_object.value = "";
            element_object.innerHTML = "";
        } else {
            handler();
        }
    }
    return;
}

// Success/failure colors used for displaying results to the user.
const results_notice_level_to_color_map = {
    warning: "#be1931",
    success: "#085d44",
};

function set_results_notice(msg, level) {
    const results_notice_field = $("#results_notice")[0];
    results_notice_field.innerHTML = msg;
    results_notice_field.style.color = results_notice_level_to_color_map[level];
    return;
}

function get_api_key_from_selected_bot() {
    return $("#bot_name").children("option:selected").val();
}

function get_selected_integration_name() {
    return $("#integration_name").children("option:selected").val();
}

function get_fixture_format(fixture_name) {
    const pieces = fixture_name.split(".");
    return pieces[pieces.length - 1];
}

function get_custom_http_headers() {
    let custom_headers = $("#custom_http_headers").val();
    if (custom_headers !== "") {
        // JSON.parse("") would trigger an error, as empty strings do not qualify as JSON.
        try {
            // Let JavaScript validate the JSON for us.
            custom_headers = JSON.stringify(JSON.parse(custom_headers));
        } catch {
            set_results_notice("Custom HTTP headers are not in a valid JSON format.", "warning");
            return undefined;
        }
    }
    return custom_headers;
}

function set_results(response) {
    /* The backend returns the JSON responses for each of the
    send_message actions included in our request (which is just 1 for
    send, but usually is several for send all).  We display these
    responses to the user in the "results" panel.

    The following is a bit messy, but it's a devtool, so ultimately OK */
    const responses = response.responses;

    let data = "Results:\n\n";
    for (const response of responses) {
        if (response.fixture_name !== undefined) {
            data += "Fixture:            " + response.fixture_name;
            data += "\nStatus Code:    " + response.status_code;
        } else {
            data += "Status Code:    " + response.status_code;
        }
        data += "\nResponse:       " + response.message + "\n\n";
    }
    $("#idp-results")[0].value = data;
}

function load_fixture_body(fixture_name) {
    /* Given a fixture name, use the loaded_fixtures dictionary to set
     * the fixture body field. */
    const integration_name = get_selected_integration_name();
    const fixture = loaded_fixtures.get(integration_name)[fixture_name];
    let fixture_body = fixture.body;
    const headers = fixture.headers;
    if (fixture_body === undefined) {
        set_results_notice("Fixture does not have a body.", "warning");
        return;
    }
    if (get_fixture_format(fixture_name) === "json") {
        // The 4 argument is pretty printer indentation.
        fixture_body = JSON.stringify(fixture_body, null, 4);
    }
    $("#fixture_body")[0].value = fixture_body;
    $("#custom_http_headers")[0].value = JSON.stringify(headers, null, 4);

    return;
}

function load_fixture_options(integration_name) {
    /* Using the integration name and loaded_fixtures object to set
    the fixture options for the fixture_names dropdown and also set
    the fixture body to the first fixture by default. */
    const fixtures_options_dropdown = $("#fixture_name")[0];
    const fixtures_names = Object.keys(loaded_fixtures.get(integration_name)).sort();

    for (const fixture_name of fixtures_names) {
        const new_dropdown_option = document.createElement("option");
        new_dropdown_option.value = fixture_name;
        new_dropdown_option.innerHTML = fixture_name;
        fixtures_options_dropdown.add(new_dropdown_option);
    }
    load_fixture_body(fixtures_names[0]);

    return;
}

function update_url() {
    /* Construct the URL that the webhook should be targeting, using
    the bot's API key and the integration name.  The stream and topic
    are both optional, and for the sake of completeness, it should be
    noted that the topic is irrelevant without specifying the
    stream. */
    const url_field = $("#URL")[0];

    const integration_name = get_selected_integration_name();
    const api_key = get_api_key_from_selected_bot();

    if (integration_name === "" || api_key === "") {
        clear_elements(["URL"]);
    } else {
        let url = url_base + integration_name + "?api_key=" + api_key;
        const stream_name = $("#stream_name").val();
        if (stream_name !== "") {
            url += "&stream=" + stream_name;
            const topic_name = $("#topic_name").val();
            if (topic_name !== "") {
                url += "&topic=" + topic_name;
            }
        }
        url_field.value = url;
        url_field.innerHTML = url;
    }

    return;
}

// API Callers: These methods handle communicating with the Python backend API.
function handle_unsuccessful_response(response) {
    try {
        const status_code = response.statusCode().status;
        response = JSON.parse(response.responseText);
        set_results_notice(`Result: (${status_code}) ${response.msg}`, "warning");
    } catch {
        // If the response is not a JSON response, then it is probably
        // Django returning an HTML response containing a stack trace
        // with useful debugging information regarding the backend
        // code.
        document.write(response.responseText);
    }
    return;
}

function get_fixtures(integration_name) {
    /* Request fixtures from the backend for any integrations that we
    don't already have fixtures cached in loaded_fixtures). */
    if (integration_name === "") {
        clear_elements([
            "custom_http_headers",
            "fixture_body",
            "fixture_name",
            "URL",
            "results_notice",
        ]);
        return;
    }

    if (loaded_fixtures.has(integration_name)) {
        load_fixture_options(integration_name);
        return;
    }

    // We don't have the fixtures for this integration; fetch them
    // from the backend.  Relative URL pattern:
    // /devtools/integrations/<integration_name>/fixtures
    channel.get({
        url: "/devtools/integrations/" + integration_name + "/fixtures",
        // Since the user may add or modify fixtures as they edit.
        idempotent: false,
        success(response) {
            loaded_fixtures.set(integration_name, response.fixtures);
            load_fixture_options(integration_name);
            return;
        },
        error: handle_unsuccessful_response,
    });

    return;
}

function send_webhook_fixture_message() {
    /* Make sure that the user is sending valid JSON in the fixture
    body and that the URL is not empty. Then simply send the fixture
    body to the target URL. */

    // Note: If the user just logged in to a different Zulip account
    // using another tab while the integrations dev panel is open,
    // then the csrf token that we have stored in the hidden input
    // element would have been expired, leading to an error message
    // when the user tries to send the fixture body.
    const csrftoken = $("#csrftoken").val();

    const url = $("#URL").val();
    if (url === "") {
        set_results_notice("URL can't be empty.", "warning");
        return;
    }

    let body = $("#fixture_body").val();
    const fixture_name = $("#fixture_name").val();
    let is_json = false;
    if (fixture_name && get_fixture_format(fixture_name) === "json") {
        try {
            // Let JavaScript validate the JSON for us.
            body = JSON.stringify(JSON.parse(body));
            is_json = true;
        } catch {
            set_results_notice("Invalid JSON in fixture body.", "warning");
            return;
        }
    }

    const custom_headers = get_custom_http_headers();

    channel.post({
        url: "/devtools/integrations/check_send_webhook_fixture_message",
        data: {url, body, custom_headers, is_json},
        beforeSend(xhr) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        },
        success(response) {
            // If the previous fixture body was sent successfully,
            // then we should change the success message up a bit to
            // let the user easily know that this fixture body was
            // also sent successfully.
            set_results(response);
            if ($("#results_notice")[0].innerHTML === "Success!") {
                set_results_notice("Success!!!", "success");
            } else {
                set_results_notice("Success!", "success");
            }
            return;
        },
        error: handle_unsuccessful_response,
    });

    return;
}

function send_all_fixture_messages() {
    /* Send all fixture messages for a given integration. */
    const url = $("#URL").val();
    const integration = get_selected_integration_name();
    if (integration === "") {
        set_results_notice("You have to select an integration first.");
        return;
    }

    const csrftoken = $("#csrftoken").val();
    channel.post({
        url: "/devtools/integrations/send_all_webhook_fixture_messages",
        data: {url, integration_name: integration},
        beforeSend(xhr) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        },
        success(response) {
            set_results(response);
        },
        error: handle_unsuccessful_response,
    });

    return;
}

// Initialization
$(() => {
    clear_elements([
        "stream_name",
        "topic_name",
        "URL",
        "bot_name",
        "integration_name",
        "fixture_name",
        "custom_http_headers",
        "fixture_body",
        "results_notice",
        "results",
    ]);

    $("#stream_name")[0].value = "Denmark";
    $("#topic_name")[0].value = "Integrations Testing";

    const potential_default_bot = $("#bot_name")[0][1];
    if (potential_default_bot !== undefined) {
        potential_default_bot.selected = true;
    }

    $("#integration_name").on("change", function () {
        clear_elements(["custom_http_headers", "fixture_body", "fixture_name", "results_notice"]);
        const integration_name = $(this).children("option:selected").val();
        get_fixtures(integration_name);
        update_url();
        return;
    });

    $("#fixture_name").on("change", function () {
        clear_elements(["fixture_body", "results_notice"]);
        const fixture_name = $(this).children("option:selected").val();
        load_fixture_body(fixture_name);
        return;
    });

    $("#send_fixture_button").on("click", () => {
        send_webhook_fixture_message();
        return;
    });

    $("#send_all_fixtures_button").on("click", () => {
        clear_elements(["results_notice"]);
        send_all_fixture_messages();
        return;
    });

    $("#bot_name").on("change", update_url);

    $("#stream_name").on("change", update_url);

    $("#topic_name").on("change", update_url);
});
<commit_msg>js: Convert static/js/portico/integrations_dev_panel.js to ES6 module.<commit_after>// Main JavaScript file for the integrations development panel at
// /devtools/integrations.

// Data Segment: We lazy load the requested fixtures from the backend
// as and when required and then cache them here.

const loaded_fixtures = new Map();
const url_base = "/api/v1/external/";

// A map defining how to clear the various UI elements.
const clear_handlers = {
    stream_name: "#stream_name",
    topic_name: "#topic_name",
    URL: "#URL",
    results_notice: "#results_notice",
    bot_name() {
        $("#bot_name").children()[0].selected = true;
    },
    integration_name() {
        $("#integration_name").children()[0].selected = true;
    },
    fixture_name() {
        $("#fixture_name").empty();
    },
    fixture_body() {
        $("#fixture_body")[0].value = "";
    },
    custom_http_headers() {
        $("#custom_http_headers")[0].value = "{}";
    },
    results() {
        $("#idp-results")[0].value = "";
    },
};

function clear_elements(elements) {
    // Supports strings (a selector to clear) or calling a function
    // (for more complex logic).
    for (const element_name of elements) {
        const handler = clear_handlers[element_name];
        if (typeof handler === "string") {
            const element_object = $(handler)[0];
            element_object.value = "";
            element_object.innerHTML = "";
        } else {
            handler();
        }
    }
    return;
}

// Success/failure colors used for displaying results to the user.
const results_notice_level_to_color_map = {
    warning: "#be1931",
    success: "#085d44",
};

function set_results_notice(msg, level) {
    const results_notice_field = $("#results_notice")[0];
    results_notice_field.innerHTML = msg;
    results_notice_field.style.color = results_notice_level_to_color_map[level];
    return;
}

function get_api_key_from_selected_bot() {
    return $("#bot_name").children("option:selected").val();
}

function get_selected_integration_name() {
    return $("#integration_name").children("option:selected").val();
}

function get_fixture_format(fixture_name) {
    const pieces = fixture_name.split(".");
    return pieces[pieces.length - 1];
}

function get_custom_http_headers() {
    let custom_headers = $("#custom_http_headers").val();
    if (custom_headers !== "") {
        // JSON.parse("") would trigger an error, as empty strings do not qualify as JSON.
        try {
            // Let JavaScript validate the JSON for us.
            custom_headers = JSON.stringify(JSON.parse(custom_headers));
        } catch {
            set_results_notice("Custom HTTP headers are not in a valid JSON format.", "warning");
            return undefined;
        }
    }
    return custom_headers;
}

function set_results(response) {
    /* The backend returns the JSON responses for each of the
    send_message actions included in our request (which is just 1 for
    send, but usually is several for send all).  We display these
    responses to the user in the "results" panel.

    The following is a bit messy, but it's a devtool, so ultimately OK */
    const responses = response.responses;

    let data = "Results:\n\n";
    for (const response of responses) {
        if (response.fixture_name !== undefined) {
            data += "Fixture:            " + response.fixture_name;
            data += "\nStatus Code:    " + response.status_code;
        } else {
            data += "Status Code:    " + response.status_code;
        }
        data += "\nResponse:       " + response.message + "\n\n";
    }
    $("#idp-results")[0].value = data;
}

function load_fixture_body(fixture_name) {
    /* Given a fixture name, use the loaded_fixtures dictionary to set
     * the fixture body field. */
    const integration_name = get_selected_integration_name();
    const fixture = loaded_fixtures.get(integration_name)[fixture_name];
    let fixture_body = fixture.body;
    const headers = fixture.headers;
    if (fixture_body === undefined) {
        set_results_notice("Fixture does not have a body.", "warning");
        return;
    }
    if (get_fixture_format(fixture_name) === "json") {
        // The 4 argument is pretty printer indentation.
        fixture_body = JSON.stringify(fixture_body, null, 4);
    }
    $("#fixture_body")[0].value = fixture_body;
    $("#custom_http_headers")[0].value = JSON.stringify(headers, null, 4);

    return;
}

function load_fixture_options(integration_name) {
    /* Using the integration name and loaded_fixtures object to set
    the fixture options for the fixture_names dropdown and also set
    the fixture body to the first fixture by default. */
    const fixtures_options_dropdown = $("#fixture_name")[0];
    const fixtures_names = Object.keys(loaded_fixtures.get(integration_name)).sort();

    for (const fixture_name of fixtures_names) {
        const new_dropdown_option = document.createElement("option");
        new_dropdown_option.value = fixture_name;
        new_dropdown_option.innerHTML = fixture_name;
        fixtures_options_dropdown.add(new_dropdown_option);
    }
    load_fixture_body(fixtures_names[0]);

    return;
}

function update_url() {
    /* Construct the URL that the webhook should be targeting, using
    the bot's API key and the integration name.  The stream and topic
    are both optional, and for the sake of completeness, it should be
    noted that the topic is irrelevant without specifying the
    stream. */
    const url_field = $("#URL")[0];

    const integration_name = get_selected_integration_name();
    const api_key = get_api_key_from_selected_bot();

    if (integration_name === "" || api_key === "") {
        clear_elements(["URL"]);
    } else {
        let url = url_base + integration_name + "?api_key=" + api_key;
        const stream_name = $("#stream_name").val();
        if (stream_name !== "") {
            url += "&stream=" + stream_name;
            const topic_name = $("#topic_name").val();
            if (topic_name !== "") {
                url += "&topic=" + topic_name;
            }
        }
        url_field.value = url;
        url_field.innerHTML = url;
    }

    return;
}

// API Callers: These methods handle communicating with the Python backend API.
function handle_unsuccessful_response(response) {
    try {
        const status_code = response.statusCode().status;
        response = JSON.parse(response.responseText);
        set_results_notice(`Result: (${status_code}) ${response.msg}`, "warning");
    } catch {
        // If the response is not a JSON response, then it is probably
        // Django returning an HTML response containing a stack trace
        // with useful debugging information regarding the backend
        // code.
        document.write(response.responseText);
    }
    return;
}

function get_fixtures(integration_name) {
    /* Request fixtures from the backend for any integrations that we
    don't already have fixtures cached in loaded_fixtures). */
    if (integration_name === "") {
        clear_elements([
            "custom_http_headers",
            "fixture_body",
            "fixture_name",
            "URL",
            "results_notice",
        ]);
        return;
    }

    if (loaded_fixtures.has(integration_name)) {
        load_fixture_options(integration_name);
        return;
    }

    // We don't have the fixtures for this integration; fetch them
    // from the backend.  Relative URL pattern:
    // /devtools/integrations/<integration_name>/fixtures
    channel.get({
        url: "/devtools/integrations/" + integration_name + "/fixtures",
        // Since the user may add or modify fixtures as they edit.
        idempotent: false,
        success(response) {
            loaded_fixtures.set(integration_name, response.fixtures);
            load_fixture_options(integration_name);
            return;
        },
        error: handle_unsuccessful_response,
    });

    return;
}

function send_webhook_fixture_message() {
    /* Make sure that the user is sending valid JSON in the fixture
    body and that the URL is not empty. Then simply send the fixture
    body to the target URL. */

    // Note: If the user just logged in to a different Zulip account
    // using another tab while the integrations dev panel is open,
    // then the csrf token that we have stored in the hidden input
    // element would have been expired, leading to an error message
    // when the user tries to send the fixture body.
    const csrftoken = $("#csrftoken").val();

    const url = $("#URL").val();
    if (url === "") {
        set_results_notice("URL can't be empty.", "warning");
        return;
    }

    let body = $("#fixture_body").val();
    const fixture_name = $("#fixture_name").val();
    let is_json = false;
    if (fixture_name && get_fixture_format(fixture_name) === "json") {
        try {
            // Let JavaScript validate the JSON for us.
            body = JSON.stringify(JSON.parse(body));
            is_json = true;
        } catch {
            set_results_notice("Invalid JSON in fixture body.", "warning");
            return;
        }
    }

    const custom_headers = get_custom_http_headers();

    channel.post({
        url: "/devtools/integrations/check_send_webhook_fixture_message",
        data: {url, body, custom_headers, is_json},
        beforeSend(xhr) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        },
        success(response) {
            // If the previous fixture body was sent successfully,
            // then we should change the success message up a bit to
            // let the user easily know that this fixture body was
            // also sent successfully.
            set_results(response);
            if ($("#results_notice")[0].innerHTML === "Success!") {
                set_results_notice("Success!!!", "success");
            } else {
                set_results_notice("Success!", "success");
            }
            return;
        },
        error: handle_unsuccessful_response,
    });

    return;
}

function send_all_fixture_messages() {
    /* Send all fixture messages for a given integration. */
    const url = $("#URL").val();
    const integration = get_selected_integration_name();
    if (integration === "") {
        set_results_notice("You have to select an integration first.");
        return;
    }

    const csrftoken = $("#csrftoken").val();
    channel.post({
        url: "/devtools/integrations/send_all_webhook_fixture_messages",
        data: {url, integration_name: integration},
        beforeSend(xhr) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        },
        success(response) {
            set_results(response);
        },
        error: handle_unsuccessful_response,
    });

    return;
}

// Initialization
$(() => {
    clear_elements([
        "stream_name",
        "topic_name",
        "URL",
        "bot_name",
        "integration_name",
        "fixture_name",
        "custom_http_headers",
        "fixture_body",
        "results_notice",
        "results",
    ]);

    $("#stream_name")[0].value = "Denmark";
    $("#topic_name")[0].value = "Integrations Testing";

    const potential_default_bot = $("#bot_name")[0][1];
    if (potential_default_bot !== undefined) {
        potential_default_bot.selected = true;
    }

    $("#integration_name").on("change", function () {
        clear_elements(["custom_http_headers", "fixture_body", "fixture_name", "results_notice"]);
        const integration_name = $(this).children("option:selected").val();
        get_fixtures(integration_name);
        update_url();
        return;
    });

    $("#fixture_name").on("change", function () {
        clear_elements(["fixture_body", "results_notice"]);
        const fixture_name = $(this).children("option:selected").val();
        load_fixture_body(fixture_name);
        return;
    });

    $("#send_fixture_button").on("click", () => {
        send_webhook_fixture_message();
        return;
    });

    $("#send_all_fixtures_button").on("click", () => {
        clear_elements(["results_notice"]);
        send_all_fixture_messages();
        return;
    });

    $("#bot_name").on("change", update_url);

    $("#stream_name").on("change", update_url);

    $("#topic_name").on("change", update_url);
});
export {};
<|endoftext|>


<commit_before>var Resource = require('./resource');
var util = require("util");
var _ = require("underscore");
var crypto = require('crypto');
var pruned = require('./json-pruned');
var fs = require('fs');
var request = require('request-promise');
var Promise = require("bluebird");

function Media(session, params) {
    Resource.apply(this, arguments);
}

util.inherits(Media, Resource);

module.exports = Media;
var Request = require('./request');
var Comment = require('./comment');
var Account = require('./account');
var Location = require('./location');
var Exceptions = require('./exceptions');

Media.prototype.parseParams = function (json) {
    var hash = {};
    var that = this;
    hash.code = json.code;
    hash.id = json.id;
    hash.likeCount = json.like_count;
    hash.hasLiked = json.has_liked;
    hash.hasMoreComments = json.has_more_comments;
    hash.photoOfYou = json.photo_of_you;
    hash.originalWidth = json.original_width;
    hash.commentCount = json.comment_count;
    hash.originalHeight = json.original_height;
    hash.mediaType = json.media_type;
    hash.deviceTimestamp = json.device_timestamp;
    hash.webLink = "https://www.instagram.com/p/" + json.code + "/"

    if(json.video_duration)
        hash.videoDuration = json.video_duration;
    if(json.filter_type)
        hash.filterType = json.filter_type;
    if(json.has_audio)
        hash.hasAudio = json.has_audio;
    if(json.view_count)
        hash.viewCount = json.view_count;
    if(_.isObject(json.location)) {
        var location = json.location;
        location.location = json.location;
        location.title = location.name;
        location.subtitle = null;
        this.location = new Location(that.session, location);
    }
    if (json.media_type === 2) {
        hash.video = {
            versions: json.video_versions,
            hasAudio: json.has_audio,
            duration: json.video_duration
        }
    }
    if (_.isObject(json.caption))
        hash.caption = json.caption.text;
    hash.takenAt = parseInt(json.taken_at) * 1000;
    if (_.isObject(json.image_versions2))
        hash.images = json.image_versions2.candidates;
    if (_.isArray(json.video_versions))
        hash.videos = json.video_versions;
    this.previewComments = _.map(json.preview_comments, function(comment) {
        return new Comment(that.session, comment);
    });
    // backward compability
    this.comments = this.previewComments;
    this.account = new Account(that.session, json.user);
    return hash;
};

Media.prototype.getParams = function () {
    return _.defaults({
        account: this.account.params,
        comments: _.pluck(this.comments, 'params'),
        location: this.location ? this.location.params : {}
    }, this._params);
};

Media.getById = function (session, id) {
    return new Request(session)
        .setMethod('GET')
        .setResource('mediaInfo', {mediaId: id})
        .send()
        .then(function(json) {
            return new Media(session, json.items[0])
        })
};

Media.getByUrl = function(session, url){
    var self = this;
    return request({
        url: 'https://api.instagram.com/oembed/',
        qs:{url:url},
        json:true
    }).then(function (response) {
        return self.getById(session, response.media_id)
    })
};

Media.likers = function(session, mediaId) {
    return new Request(session)
        .setMethod('GET')
        .setResource('mediaLikes', {mediaId: mediaId})
        .send()
        .then(function(data) {
            return _.map(data.users, function (user) {
                return new Account(session, user);
            });
        });
};

Media.delete = function(session, mediaId) {
    return new Request(session)
        .setMethod('POST')
        .setResource('mediaDeletePhoto', {mediaId: mediaId})
        .setData({
            media_id: mediaId
        })
        .generateUUID()
        .signPayload()
        .send()
        .then(function (json) {
            if(json.did_delete) return;
            throw new Exceptions.RequestError({
                messaage: 'Not posible to delete medium!'
            })
        })
};

Media.edit = function(session, mediaId, caption, userTags) {
    var requestPayload = {
        media_id: mediaId,
        caption_text: caption
    };

    if (userTags) {
        requestPayload.usertags = userTags;
    }

    return new Request(session)
        .setMethod('POST')
        .setResource('mediaEdit', {mediaId: mediaId})
        .setData(requestPayload)
        .generateUUID()
        .signPayload()
        .send()
        .then(function (json) {
            if(json.media.caption_is_edited) {
                return new Media(session, json.media);
            }
            throw new Exceptions.RequestError({
                messaage: 'Edit media not successful!'
            });
        });
};

Media.configurePhoto = function (session, uploadId, caption, width, height) {
    if(_.isEmpty(uploadId))
        throw new Error("Upload argument must be upload valid upload id");
    if(!caption) caption = "";
    if(!width) width = 800;
    if(!height) height = 800;
    const CROP = 1;
    session.getAccountId()
        .then(function(accountId){
        var payload = pruned({
            source_type: "4",
            caption: caption,
            upload_id: uploadId,
            _uid:accountId.toString(),
            device: session.device.payload,
            edits: {
                crop_original_size:["$width","$height"],
                crop_center: ["$zero","$negativeZero"],
                crop_zoom: "$crop"
            },
            extra: {
                source_width: width,
                source_height: height
            }
        });
        payload = payload.replace(/\"\$width\"/gi, width.toFixed(1));
        payload = payload.replace(/\"\$height\"/gi, height.toFixed(1));
        payload = payload.replace(/\"\$zero\"/gi, (0).toFixed(1));
        payload = payload.replace(/\"\$negativeZero\"/gi, "-" + (0).toFixed(1));
        payload = payload.replace(/\"\$crop\"/gi, CROP.toFixed(1));

        return new Request(session)
            .setMethod('POST')
            .setResource('mediaConfigure')
            .setBodyType('form')
            .setData(JSON.parse(payload))
            .generateUUID()
            .signPayload()
            .send()
            .then(function(json) {
                return new Media(session, json.media)
            })
    })
};

Media.configureVideo = function (session, uploadId, caption, durationms, delay) {
    if(_.isEmpty(uploadId))
        throw new Error("Upload argument must be upload valid upload id");
    if(typeof(durationms)==='undefined')
        throw new Error("Durationms argument must be upload valid video duration");
    var duration = durationms/1000;
    if(!caption) caption = "";
    if(!delay || typeof delay != "number") delay = 6500;
    return Promise.delay(delay)
        .then(function(){
            return session.getAccountId()
        })
        .then(function(accountId){
            var payload = pruned({
                "filter_type": "0",
                "source_type": "3",
                "video_result": "deprecated",
                "_uid":accountId.toString(),
                "caption": caption,
                "upload_id": uploadId.toString(),
                "device":{
                    "manufacturer":session.device.info.manufacturer,
                    "model":session.device.info.model,
                    "android_version":session.device._api,
                    "android_release":session.device._release
                },
                "length": duration,
                "clips": [
                    {
                        "length": duration,
                        "source_type": "3",
                        "camera_position": "back"
                    }
                ],
                "audio_muted": false,
                "poster_frame_index": 0
            });

            return new Request(session)
                .setMethod('POST')
                .setResource('videoConfigure')
                .setBodyType('form')
                .setData(JSON.parse(payload))
                .generateUUID()
                .signPayload()
                .send()
                .then(function(json) {
                    return new Media(session, json.media)
                })
                .catch(Exceptions.TranscodeTimeoutError,function(error){
                    //Well, we just want to repeat our request. Dunno why this is happening and we should not let our users deal with this crap themselves.
                    return Media.configureVideo(session,uploadId,caption,durationms,delay);
                })
    })
};<commit_msg>fixes issue #131<commit_after>var Resource = require('./resource');
var util = require("util");
var _ = require("underscore");
var crypto = require('crypto');
var pruned = require('./json-pruned');
var fs = require('fs');
var request = require('request-promise');
var Promise = require("bluebird");

function Media(session, params) {
    Resource.apply(this, arguments);
}

util.inherits(Media, Resource);

module.exports = Media;
var Request = require('./request');
var Comment = require('./comment');
var Account = require('./account');
var Location = require('./location');
var Exceptions = require('./exceptions');

Media.prototype.parseParams = function (json) {
    var hash = {};
    var that = this;
    hash.code = json.code;
    hash.id = json.id;
    hash.likeCount = json.like_count;
    hash.hasLiked = json.has_liked;
    hash.hasMoreComments = json.has_more_comments;
    hash.photoOfYou = json.photo_of_you;
    hash.originalWidth = json.original_width;
    hash.commentCount = json.comment_count;
    hash.originalHeight = json.original_height;
    hash.mediaType = json.media_type;
    hash.deviceTimestamp = json.device_timestamp;
    hash.webLink = "https://www.instagram.com/p/" + json.code + "/"

    if(json.video_duration)
        hash.videoDuration = json.video_duration;
    if(json.filter_type)
        hash.filterType = json.filter_type;
    if(json.has_audio)
        hash.hasAudio = json.has_audio;
    if(json.view_count)
        hash.viewCount = json.view_count;
    if(_.isObject(json.location)) {
        var location = json.location;
        location.location = json.location;
        location.title = location.name;
        location.subtitle = null;
        this.location = new Location(that.session, location);
    }
    if (json.media_type === 2) {
        hash.video = {
            versions: json.video_versions,
            hasAudio: json.has_audio,
            duration: json.video_duration
        }
    }
    if (_.isObject(json.caption))
        hash.caption = json.caption.text;
    hash.takenAt = parseInt(json.taken_at) * 1000;
    if (_.isObject(json.image_versions2))
        hash.images = json.image_versions2.candidates;
    if (_.isArray(json.video_versions))
        hash.videos = json.video_versions;
    this.previewComments = _.map(json.preview_comments, function(comment) {
        return new Comment(that.session, comment);
    });
    // backward compability
    this.comments = this.previewComments;
    this.account = new Account(that.session, json.user);
    return hash;
};

Media.prototype.getParams = function () {
    return _.defaults({
        account: this.account.params,
        comments: _.pluck(this.comments, 'params'),
        location: this.location ? this.location.params : {}
    }, this._params);
};

Media.getById = function (session, id) {
    return new Request(session)
        .setMethod('GET')
        .setResource('mediaInfo', {mediaId: id})
        .send()
        .then(function(json) {
            return new Media(session, json.items[0])
        })
};

Media.getByUrl = function(session, url){
    var self = this;
    return request({
        url: 'https://api.instagram.com/oembed/',
        qs:{url:url},
        json:true
    }).then(function (response) {
        return self.getById(session, response.media_id)
    })
};

Media.likers = function(session, mediaId) {
    return new Request(session)
        .setMethod('GET')
        .setResource('mediaLikes', {mediaId: mediaId})
        .send()
        .then(function(data) {
            return _.map(data.users, function (user) {
                return new Account(session, user);
            });
        });
};

Media.delete = function(session, mediaId) {
    return new Request(session)
        .setMethod('POST')
        .setResource('mediaDeletePhoto', {mediaId: mediaId})
        .setData({
            media_id: mediaId
        })
        .generateUUID()
        .signPayload()
        .send()
        .then(function (json) {
            if(json.did_delete) return;
            throw new Exceptions.RequestError({
                messaage: 'Not posible to delete medium!'
            })
        })
};

Media.edit = function(session, mediaId, caption, userTags) {
    var requestPayload = {
        media_id: mediaId,
        caption_text: caption
    };

    if (userTags) {
        requestPayload.usertags = userTags;
    }

    return new Request(session)
        .setMethod('POST')
        .setResource('mediaEdit', {mediaId: mediaId})
        .setData(requestPayload)
        .generateUUID()
        .signPayload()
        .send()
        .then(function (json) {
            if(json.media.caption_is_edited) {
                return new Media(session, json.media);
            }
            throw new Exceptions.RequestError({
                messaage: 'Edit media not successful!'
            });
        });
};

Media.configurePhoto = function (session, uploadId, caption, width, height) {
    if(_.isEmpty(uploadId))
        throw new Error("Upload argument must be upload valid upload id");
    if(!caption) caption = "";
    if(!width) width = 800;
    if(!height) height = 800;
    const CROP = 1;
    return session.getAccountId()
        .then(function(accountId){
            var payload = pruned({
                source_type: "4",
                caption: caption,
                upload_id: uploadId,
                _uid: accountId.toString(),
                device: session.device.payload,
                edits: {
                    crop_original_size:["$width","$height"],
                    crop_center: ["$zero","$negativeZero"],
                    crop_zoom: "$crop"
                },
                extra: {
                    source_width: width,
                    source_height: height
                }
            });
            payload = payload.replace(/\"\$width\"/gi, width.toFixed(1));
            payload = payload.replace(/\"\$height\"/gi, height.toFixed(1));
            payload = payload.replace(/\"\$zero\"/gi, (0).toFixed(1));
            payload = payload.replace(/\"\$negativeZero\"/gi, "-" + (0).toFixed(1));
            payload = payload.replace(/\"\$crop\"/gi, CROP.toFixed(1));

            return new Request(session)
                .setMethod('POST')
                .setResource('mediaConfigure')
                .setBodyType('form')
                .setData(JSON.parse(payload))
                .generateUUID()
                .signPayload()
                .send()
        })
        .then(function(json) {
            return new Media(session, json.media)
        })
};

Media.configureVideo = function (session, uploadId, caption, durationms, delay) {
    if(_.isEmpty(uploadId))
        throw new Error("Upload argument must be upload valid upload id");
    if(typeof(durationms)==='undefined')
        throw new Error("Durationms argument must be upload valid video duration");
    var duration = durationms/1000;
    if(!caption) caption = "";
    if(!delay || typeof delay != "number") delay = 6500;
    return Promise.delay(delay)
        .then(function(){
            return session.getAccountId()
        })
        .then(function(accountId){
            var payload = pruned({
                "filter_type": "0",
                "source_type": "3",
                "video_result": "deprecated",
                "_uid":accountId.toString(),
                "caption": caption,
                "upload_id": uploadId.toString(),
                "device":{
                    "manufacturer":session.device.info.manufacturer,
                    "model":session.device.info.model,
                    "android_version":session.device._api,
                    "android_release":session.device._release
                },
                "length": duration,
                "clips": [
                    {
                        "length": duration,
                        "source_type": "3",
                        "camera_position": "back"
                    }
                ],
                "audio_muted": false,
                "poster_frame_index": 0
            });

            return new Request(session)
                .setMethod('POST')
                .setResource('videoConfigure')
                .setBodyType('form')
                .setData(JSON.parse(payload))
                .generateUUID()
                .signPayload()
                .send()
                .then(function(json) {
                    return new Media(session, json.media)
                })
                .catch(Exceptions.TranscodeTimeoutError,function(error){
                    //Well, we just want to repeat our request. Dunno why this is happening and we should not let our users deal with this crap themselves.
                    return Media.configureVideo(session,uploadId,caption,durationms,delay);
                })
    })
};
<|endoftext|>


<commit_before>var lookup_prefix = 'img/',
    lookup_refs = [],
    lookup = {
    ' ': 'tile.png',
    'S': 'tile.png',
    'B': 'block.png',
    '.': 'destructible.png',
    '+': 'flame_cross.png',
    '-': 'flame_hz.png',
    '>': 'flame_hz.png',
    '<': 'flame_hz.png',
    '|': 'flame_vt.png',
    '^': 'flame_vt.png',
    'v': 'flame_vt.png',
    '1': 'p1.png',
    '2': 'p2.png',
    '3': 'p3.png',
    '4': 'p4.png',
    'b': 'powerup_bomb.png',
    'f': 'powerup_flame.png',
};

function keys_register(uid) {
    $(document).keydown(function(e) {
        if (e.keyCode == 32) {
            $.update('/player/' + uid, JSON.stringify({'action': 'BOMB'}));
            return false;
        } else if (e.keyCode == 37) {
            $.update('/player/' + uid, JSON.stringify({'action': 'LEFT'}));
            return false;
        } else if (e.keyCode == 38) {
            $.update('/player/' + uid, JSON.stringify({'action': 'UP'}));
            return false;
        } else if (e.keyCode == 39) {
            $.update('/player/' + uid, JSON.stringify({'action': 'RIGHT'}));
            return false;
        } else if (e.keyCode == 40) {
            $.update('/player/' + uid, JSON.stringify({'action': 'DOWN'}));
            return false;
        }
    });
}

function player_create() {
    $.create('/player', JSON.stringify({'name': 'js'}), function(resp) {
        keys_register(resp.uid);
        game_update(resp.uid);
    });
}

function build_table() {
    $.read('/game', function(resp) {
        var table = document.getElementById('game'),
            table_row = document.createElement('tr'),
            table_cell,
            img;

        for (i = 0; i < resp.length; i++) {
            var c = resp.charAt(i);
            if (c == "\n") {
                table.appendChild(table_row);
                table_row = document.createElement('tr');
                continue;
            }
            table_cell = document.createElement('td');
            img        = document.createElement('img');
            lookup_refs[i] = img;
            img.setAttribute('src', lookup_prefix + lookup[c]);
            table_cell.appendChild(img);
            table_row.appendChild(table_cell);
        }
    });
}

function game_update(uid) {
    // refresh loop
    /*setInterval(function() {
        var last_game;
        $.read('/player/' + uid, function(resp) {
            for (i = 0; i < resp.game.length; i++) {
                var c  = resp.game.charAt(i);
                var cl = last_game.charAt(i);

                if (c == "\n") {
                    alert('newline');
                    continue;
                }

                if (c != cl) {
                    lookup_refs[i].setAttribute('src', lookup_prefix + lookup[c]);
                }
            }
            last_game = resp.game;
        });
    }, 100);*/
}

$(function() {
    player_create();
    build_table();
});
<commit_msg>Rework client3 to use proper js OO and make it work<commit_after>function Game() {
    var uid,
        last_frame,
        img_refs = [],
        lookup_prefix = 'img/',
        lookup = {
            ' ': 'tile.png',
            'S': 'tile.png',
            'B': 'block.png',
            'x': 'bomb.png',
            '.': 'destructible.png',
            '+': 'flame_cross.png',
            '-': 'flame_hz.png',
            '>': 'flame_hz.png',
            '<': 'flame_hz.png',
            '|': 'flame_vt.png',
            '^': 'flame_vt.png',
            'v': 'flame_vt.png',
            '1': 'p1.png',
            '2': 'p2.png',
            '3': 'p3.png',
            '4': 'p4.png',
            'b': 'powerup_bomb.png',
            'f': 'powerup_flame.png',
        };

    var player_create = function () {
        $.create('/player', JSON.stringify({'name': 'js'}), function(resp) {
            uid = resp.uid;
        });
    }

    var keys_register = function () {
        $(document).keydown(function(e) {
            if (e.keyCode == 32) {
                $.update('/player/' + uid, JSON.stringify({'action': 'BOMB'}));
                return false;
            } else if (e.keyCode == 37) {
                $.update('/player/' + uid, JSON.stringify({'action': 'LEFT'}));
                return false;
            } else if (e.keyCode == 38) {
                $.update('/player/' + uid, JSON.stringify({'action': 'UP'}));
                return false;
            } else if (e.keyCode == 39) {
                $.update('/player/' + uid, JSON.stringify({'action': 'RIGHT'}));
                return false;
            } else if (e.keyCode == 40) {
                $.update('/player/' + uid, JSON.stringify({'action': 'DOWN'}));
                return false;
            }
        });
    }

    var table_build = function () {
        $.read('/game', function(resp) {
            var table = document.getElementById('game'),
                table_row = document.createElement('tr'),
                table_cell,
                img;

            last_frame = resp;

            for (i = 0; i < resp.length; i++) {
                var c = resp.charAt(i);
                if (c == "\n") {
                    table.appendChild(table_row);
                    table_row = document.createElement('tr');
                    continue;
                }
                table_cell = document.createElement('td');
                img        = document.createElement('img');
                img_refs[i] = img;
                img.setAttribute('src', lookup_prefix + lookup[c]);
                table_cell.appendChild(img);
                table_row.appendChild(table_cell);
            }
        });
    }

    var table_update = function () {
        $.read('/player/' + uid, function(resp) {
            for (i = 0; i < resp.game.length; i++) {
                var c  = resp.game.charAt(i);
                if (c == "\n") {
                    continue;
                }
                var cl = last_frame.charAt(i);

                if (c != cl) {
                    img_refs[i].setAttribute('src', lookup_prefix + lookup[c]);
                }
            }
            last_frame = resp.game;
        });
    }

    player_create();
    keys_register();
    table_build();

    setInterval(function() {
        table_update();
    }, 100);
}

$(function() {
    Game();
});
<|endoftext|>


<commit_before>import { html, css } from 'lit-element';
import { t, ut } from '../directives/i18n';
import { gridCss } from '../styles';

const features = [
  'isomorphic',
  'versatile',
  'declarative',
  'typesafe',
  'treeshakable'
];

function renderFeature(feature) {
  return html`

${t(`features.${feature}.title`)}

${ut(`features.${feature}.description`)}

  `;
}

const template = () => html`

${features.map(renderFeature)}

`;

template.styles = [
  gridCss,
  css`
    .features {
      flex-wrap: wrap;
      justify-content: center;
      padding: 2rem 0;
    }

    .feature {
      padding: 1rem;
      max-width: 200px;
      flex-basis: 200px;
    }

    .feature h3 {
      font-size: 1.4rem;
    }
  `
];

export default template;
<commit_msg>docs(app): fixes grid css on home page<commit_after>import { html, css, unsafeCSS } from 'lit-element';
import { t, ut } from '../directives/i18n';

const features = [
  'isomorphic',
  'versatile',
  'declarative',
  'typesafe',
  'treeshakable'
];

function renderFeature(feature) {
  return html`

${t(`features.${feature}.title`)}

${ut(`features.${feature}.description`)}

  `;
}

const template = () => html`

${features.map(renderFeature)}

`;

template.styles = [
  css`
    .features {
      padding: 1rem 0;
      display: -ms-grid;
      display: grid;
      justify-content: center;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      -ms-grid-columns: ${unsafeCSS(features.map(() => 'minmax(200px, 1fr)').join(' '))};
    }

    .feature {
      padding: 1rem;
    }

    .feature h3 {
      font-size: 1.4rem;
    }

    .feature p:last-child {
      margin-bottom: 0;
    }
  `
];

export default template;
<|endoftext|>


<commit_before>/* Main app file for CEL project
 * Mostly stolen from requirejs examples: http://requirejs.org/docs/api.html
 * See http://requirejs.org/docs/api.html for details
 */

define(['jquery', 'Leaflet', '../lib/response', 'Handlebars', 'bootstrap',
        'jquery-ui', 'jquery-cookie', CEL.serverVars.gmapRequire],
function($, L, Response, Handlebars) {
    'use strict';

    var breakpoints = {
        mobile: 420,
        tablet: 767,
        desktop: 1024
    };

    // Hide the address bar on mobile browsers
    // Solution from: http://mobile.tutsplus.com/tutorials/mobile-web-apps/remove-address-bar/
    function hideAddressBar() {
        if(!window.location.hash) {
            if(document.height < window.outerHeight) {
                document.body.style.height = (window.outerHeight + 50) + 'px';
            }
            setTimeout(function() {
                window.scrollTo(0, 1);
            }, 50);
        }
    }
    $(window).on('load', function() {
        if(!window.pageYOffset) {
            hideAddressBar();
        }
    }).on('orientationchange', hideAddressBar);

    $(document).ready(function() {

        // AUTOCOMPLETE
        var $autocomplete = $('.autocomplete-searchbox');

        // autocomplete helper function that makes the request to
        //  the google maps geocoder API
        function getGeocoderAddresses(request, response) {
            var geocoder = new google.maps.Geocoder(),
                acSettings = CEL.serverVars.autocomplete,
                // Static bounds. Ideally these lat/lng pairs should be set to be slightly larger
                //      than the bounding box of all Locations in the database.
                northEast = new google.maps.LatLng(acSettings.nelat, acSettings.nelng),
                southWest = new google.maps.LatLng(acSettings.swlat, acSettings.swlng),
                bounds = new google.maps.LatLngBounds(southWest, northEast);

            geocoder.geocode(
                {
                    address: request.term,
                    bounds: bounds,
                    region: 'US'
                },
                function(results, status) {
                    var cleanedResults = [],
                        result,
                        lat,
                        lon,
                        resultLocation,
                        likelyResult,
                        $element = $('.autocomplete-searchbox');

                    for (var i in results) {
                        result = results[i];
                        lat = result.geometry.location.lat();
                        lon = result.geometry.location.lng();
                        resultLocation = new google.maps.LatLng(lat, lon);
                        if (bounds.contains(resultLocation)) {
                            cleanedResults.push({
                                lat: lat,
                                lon: lon,
                                label: result.formatted_address,
                                value: result.formatted_address
                            });
                        }
                    }
                    if (cleanedResults.length === 0) {
                        cleanedResults.push({
                            label: "No Results",
                            value: "No Results"
                        });
                    } else {
                        // push first result to input element
                        //      just like in autocomplete success handler below
                        likelyResult = cleanedResults[0];
                        $element.data({
                            lat: likelyResult.lat,
                            lon: likelyResult.lon
                        });
                    }
                    response(cleanedResults);
                }
             );
        }

        /*
         * Sets window.location.href to appropriate value based on selected autocomplete result
         * See: http://api.jqueryui.com/autocomplete/#event-select
         *      for details on the ui object
         */
        var submitAutocomplete = function(ui) {
            if (ui.item) {
                if (ui.item.type === 'location') {
                    window.location.href = getUrl(
                        'single-location', { location: ui.item.id });
                    return;
                } else if (ui.item.type === 'neighborhood') {
                    window.location.href = getUrl(
                        'browse', { type: 'neighborhood', neighborhood: ui.item.id });
                    return;
                } else if (ui.item.lat && ui.item.lon) {
                    window.location.href = getUrl(
                        'browse', { type: 'latlng', lat: ui.item.lat, lng: ui.item.lon, zoom: 14 });
                    return;
                }
            }
            window.location.href = getUrl('browse');
        };

        /*
         *  Spoof the jquery ui select function ui object using the input element data attributes
         */
        var spoofSubmitAutocomplete = function() {
            var ui = {
                item: $autocomplete.data()
            };
            submitAutocomplete(ui);
        };

        /*
         * Submit the first autocomplete result on button click if none is populated
         */
        $('.autocomplete-submit').on('click', function(e) {
            e.preventDefault();
            spoofSubmitAutocomplete();
        });

        /*
         * Submit the first autocomplete result on enter if no result is populated
         * This also overrides the functionality in the select callback below
         */
        $autocomplete.on('keyup', function(e) {
            if (e.which === 13) {
                spoofSubmitAutocomplete();
            }
        });

        /*
         * Autocomplete widget setup and all relevant callbacks
         */
        var autocomplete = $autocomplete.autocomplete({
            source: function(request, response) {
                var self = this;
                $.ajax({
                    url: '/api/autocomplete/' + encodeURIComponent(request.term),
                    success: function(json) {
                        if (!json || !json.response) {
                            return;
                        }
                        var data = json.response;
                        if (data.length > 0) {
                            // push first result as data attrs of the input element
                            //      so we can access it externally
                            var likelyResult = data[0];
                            $('.autocomplete-searchbox').data({
                                id: likelyResult.id,
                                type: likelyResult.type
                            });
                            // use returned schools and neighborhoods
                            response($.map(data, function(value) {
                                return {
                                    id: value.id,
                                    type: value.type,
                                    label: value.name,
                                    value: value.name
                                };
                            }));
                        } else {
                            getGeocoderAddresses(request, response);
                        }
                    },
                    error: function(e, status, error) {
                        getGeocoderAddresses(request, response);
                    }
                });
            },
            select: function(event, ui) {
                submitAutocomplete(ui);
            },
            focus: function(event, ui) {
                // mirror data to the input element so the proper item is submitted
                $('.autocomplete-searchbox').data({
                    id: ui.item.id,
                    type: ui.item.type,
                    lat: ui.item.lat,
                    lon: ui.item.lon
                });
            },
            minLength: 2        // do not make a request until we have typed two chars
        });
        // END AUTOCOMPLETE
    });

    // Setup Response stuff
    var breakpointsArray = [
        0,
        breakpoints.mobile,
        breakpoints.tablet,
        breakpoints.desktop
    ];
    Response.create({ mode: 'markup', prefix: 'r', breakpoints: breakpointsArray });
    Response.create({ mode: 'src',  prefix: 'src', breakpoints: breakpointsArray });

    // Handlebars helpers

    /**
     * Helper for resolving static urls in handlebars templates
     * @param { URL to convert to static, same as argument for django static template function } url
     * @return { full static url }
     */
    Handlebars.registerHelper('static', function(url) {
        return CEL.serverVars.staticRoot + url;
    });

    /**
     * Central api for getting urls for the app
     * @param { logical name of the endpoint } name
     * @param { Options for creating the url, depends on name } opts
     * @return { URL string for request }
     */
    var getUrl = function (name, opts) {
        var url = '';
        switch (name) {
            case 'origin':
                // IE < 9 doesn't define location.origin
                return window.location.origin ||
                    (window.location.protocol + "//" + window.location.host);
            case 'location-api':
                // requires opts.locations to be comma separated string or
                //      array of integers
                url = '/' + ($.cookie('django_language') || CEL.serverVars.default_language) +
                    '/api/location/';
                if (opts && opts.locations) {
                    url += opts.locations.toString() + '/';
                }
                return url;
            case 'neighborhood-api':
                return '/' + ($.cookie('django_language') || CEL.serverVars.default_language) +
                    '/api/neighborhood/';
            case 'neighborhoods-topo':
                return '/static/js/neighborhoods-topo.json';
            case 'neighborhoods-geojson':
                return '/static/js/neighborhoods.json';
            case 'browse':
                if (!opts) {
                    return '/search/';
                }
                switch (opts.type) {
                    case 'latlng':
                        url = '/search/?lat=' + opts.lat + '&lng=' + opts.lng;
                        if (opts.zoom) {
                            url += '&zoom=' + opts.zoom;
                        }
                        return url;
                    case 'geo-latlng':
                        url = '/search/?geolat=' + opts.lat + '&geolng=' + opts.lng;
                        return url;
                    case 'neighborhood':
                        return '/search/?neighborhood=' + opts.neighborhood;
                    case 'location':
                        return '/search/?location=' + opts.location;
                    default:
                        break;
                }
                break;
            case 'single-location':
                return '/location/' + opts.location + '/';
            case 'starred':
                url = '/starred/';
                if (opts && opts.locations) {
                    url += opts.locations.toString() + '/';
                }
                return url;
            case 'icon-school':
                return '/static/img/leaflet-icons/school.png';
            case 'icon-school-accredited':
                return '/static/img/leaflet-icons/school-accredited.png';
            case 'icon-school-starred':
                return '/static/img/leaflet-icons/school-starred.png';
            case 'icon-school-accredited-starred':
                return '/static/img/leaflet-icons/school-accredited-starred.png';
            case 'icon-center':
                return '/static/img/leaflet-icons/center.png';
            case 'icon-center-accredited':
                return '/static/img/leaflet-icons/center-accredited.png';
            case 'icon-center-starred':
                return '/static/img/leaflet-icons/center-starred.png';
            case 'icon-center-accredited-starred':
                return '/static/img/leaflet-icons/center-accredited-starred.png';
            case 'icon-geolocation':
                return '/static/img/leaflet-icons/geocode.png';
            default:
                break;
        }
        throw 'Unknown URL endpoint';
    };

    // geolocation
    if ('geolocation' in navigator) {
        $(document).ready(function() {
            $('.geolocation-button').bind('click', function(e) {
                e.preventDefault();
                navigator.geolocation.getCurrentPosition(function(position) {
                    window.location.href = getUrl(
                        'browse',
                        {
                            type: 'geo-latlng',
                            lat: position.coords.latitude,
                            lng: position.coords.longitude
                        }
                    );
                }, function(e) {
                    e.preventDefault();
                    alert(gettext('Please enable geolocation services.'));
                });
            });
        });
    } else {
        $('.geolocation-button').hide();
    }

    // Set up social sharing behavior
    // The options argument must be an object that contains both a url and a title,
    // which are used to construct the appropriate sharing links.
    $('#share-modal').on('init-modal', function(e, options) {
        // a lot of these urls won't work when passing 'localhost' urls, but once we
        // move to a publically accessible url, they will work just fine
        var templates = {
                mail: 'mailto:?body={{ url }}&subject={{ title }}',
                facebook: 'http://facebook.com/sharer.php?s=100&p[url]={{ url }}&p[title]={{ title }}',
                twitter: 'https://twitter.com/share?text={{ title }}%20{{ url }}',

                // looks like google recently broke their permalink that allows adding text:
                // 'https://m.google.com/app/plus/x/?v=compose&content={{ title }}%20{{ url }}'
                // until they fix it, the following will work, but will just share the url
                gplus: 'https://plus.google.com/share?url={{ url }}'
            },
            $modal = $(this),
            $text = $('#social-text');

        // fill the input box in with the url
        $text.attr('href', options.url).text(options.url);

        // a url is generated for each template defined in 'templates'.
        // to add a new service, create a link with the id 'social-[service]',
        // and add a template to the 'templates' object.
        $.each(templates, function (service, template) {
            $('#social-' + service).attr('href', Handlebars.compile(template)(options));
        });

        // display the modal
        $modal.modal('show');
    });

    return {
        getUrl: getUrl,

        breakpoints: breakpoints,

        // Stolen from _.js v1.5.1
        // https://github.com/jashkenas/underscore/blob/dc5a3fa0133b7000c36ba76a413139c63f646789/underscore.js
        // See project root for license
        //
        // Returns a function, that, as long as it continues to be invoked, will not
        // be triggered. The function will be called after it stops being called for
        // N milliseconds. If `immediate` is passed, trigger the function on the
        // leading edge, instead of the trailing.
        debounce: function(func, wait, immediate) {
            var result,
                timeout = null;
            return function() {
                var context = this,
                    args = arguments,
                    later = function() {
                        timeout = null;
                        if (!immediate) result = func.apply(context, args);
                    },
                    callNow = immediate && !timeout;
                    clearTimeout(timeout);
                    timeout = setTimeout(later, wait);
                    if (callNow) {
                        result = func.apply(context, args);
                    }

                return result;
            };
        }
    };
});
<commit_msg>Call hideAddressBar only on mobile<commit_after>/* Main app file for CEL project
 * Mostly stolen from requirejs examples: http://requirejs.org/docs/api.html
 * See http://requirejs.org/docs/api.html for details
 */

define(['jquery', 'Leaflet', '../lib/response', 'Handlebars', 'bootstrap',
        'jquery-ui', 'jquery-cookie', CEL.serverVars.gmapRequire],
function($, L, Response, Handlebars) {
    'use strict';

    var breakpoints = {
        mobile: 420,
        tablet: 767,
        desktop: 1024
    };

    // Hide the address bar on mobile browsers
    // Solution from: http://mobile.tutsplus.com/tutorials/mobile-web-apps/remove-address-bar/
    function hideAddressBar() {
        if(!window.location.hash) {
            if(document.height < window.outerHeight) {
                document.body.style.height = (window.outerHeight + 50) + 'px';
            }
            setTimeout(function() {
                window.scrollTo(0, 1);
            }, 50);
        }
    }
    if ('ontouchstart' in document.documentElement) {
        $(window).on('load', function() {
            if(!window.pageYOffset) {
                hideAddressBar();
            }
        }).on('orientationchange', hideAddressBar);
    }

    $(document).ready(function() {

        // AUTOCOMPLETE
        var $autocomplete = $('.autocomplete-searchbox');

        // autocomplete helper function that makes the request to
        //  the google maps geocoder API
        function getGeocoderAddresses(request, response) {
            var geocoder = new google.maps.Geocoder(),
                acSettings = CEL.serverVars.autocomplete,
                // Static bounds. Ideally these lat/lng pairs should be set to be slightly larger
                //      than the bounding box of all Locations in the database.
                northEast = new google.maps.LatLng(acSettings.nelat, acSettings.nelng),
                southWest = new google.maps.LatLng(acSettings.swlat, acSettings.swlng),
                bounds = new google.maps.LatLngBounds(southWest, northEast);

            geocoder.geocode(
                {
                    address: request.term,
                    bounds: bounds,
                    region: 'US'
                },
                function(results, status) {
                    var cleanedResults = [],
                        result,
                        lat,
                        lon,
                        resultLocation,
                        likelyResult,
                        $element = $('.autocomplete-searchbox');

                    for (var i in results) {
                        result = results[i];
                        lat = result.geometry.location.lat();
                        lon = result.geometry.location.lng();
                        resultLocation = new google.maps.LatLng(lat, lon);
                        if (bounds.contains(resultLocation)) {
                            cleanedResults.push({
                                lat: lat,
                                lon: lon,
                                label: result.formatted_address,
                                value: result.formatted_address
                            });
                        }
                    }
                    if (cleanedResults.length === 0) {
                        cleanedResults.push({
                            label: "No Results",
                            value: "No Results"
                        });
                    } else {
                        // push first result to input element
                        //      just like in autocomplete success handler below
                        likelyResult = cleanedResults[0];
                        $element.data({
                            lat: likelyResult.lat,
                            lon: likelyResult.lon
                        });
                    }
                    response(cleanedResults);
                }
             );
        }

        /*
         * Sets window.location.href to appropriate value based on selected autocomplete result
         * See: http://api.jqueryui.com/autocomplete/#event-select
         *      for details on the ui object
         */
        var submitAutocomplete = function(ui) {
            if (ui.item) {
                if (ui.item.type === 'location') {
                    window.location.href = getUrl(
                        'single-location', { location: ui.item.id });
                    return;
                } else if (ui.item.type === 'neighborhood') {
                    window.location.href = getUrl(
                        'browse', { type: 'neighborhood', neighborhood: ui.item.id });
                    return;
                } else if (ui.item.lat && ui.item.lon) {
                    window.location.href = getUrl(
                        'browse', { type: 'latlng', lat: ui.item.lat, lng: ui.item.lon, zoom: 14 });
                    return;
                }
            }
            window.location.href = getUrl('browse');
        };

        /*
         *  Spoof the jquery ui select function ui object using the input element data attributes
         */
        var spoofSubmitAutocomplete = function() {
            var ui = {
                item: $autocomplete.data()
            };
            submitAutocomplete(ui);
        };

        /*
         * Submit the first autocomplete result on button click if none is populated
         */
        $('.autocomplete-submit').on('click', function(e) {
            e.preventDefault();
            spoofSubmitAutocomplete();
        });

        /*
         * Submit the first autocomplete result on enter if no result is populated
         * This also overrides the functionality in the select callback below
         */
        $autocomplete.on('keyup', function(e) {
            if (e.which === 13) {
                spoofSubmitAutocomplete();
            }
        });

        /*
         * Autocomplete widget setup and all relevant callbacks
         */
        var autocomplete = $autocomplete.autocomplete({
            source: function(request, response) {
                var self = this;
                $.ajax({
                    url: '/api/autocomplete/' + encodeURIComponent(request.term),
                    success: function(json) {
                        if (!json || !json.response) {
                            return;
                        }
                        var data = json.response;
                        if (data.length > 0) {
                            // push first result as data attrs of the input element
                            //      so we can access it externally
                            var likelyResult = data[0];
                            $('.autocomplete-searchbox').data({
                                id: likelyResult.id,
                                type: likelyResult.type
                            });
                            // use returned schools and neighborhoods
                            response($.map(data, function(value) {
                                return {
                                    id: value.id,
                                    type: value.type,
                                    label: value.name,
                                    value: value.name
                                };
                            }));
                        } else {
                            getGeocoderAddresses(request, response);
                        }
                    },
                    error: function(e, status, error) {
                        getGeocoderAddresses(request, response);
                    }
                });
            },
            select: function(event, ui) {
                submitAutocomplete(ui);
            },
            focus: function(event, ui) {
                // mirror data to the input element so the proper item is submitted
                $('.autocomplete-searchbox').data({
                    id: ui.item.id,
                    type: ui.item.type,
                    lat: ui.item.lat,
                    lon: ui.item.lon
                });
            },
            minLength: 2        // do not make a request until we have typed two chars
        });
        // END AUTOCOMPLETE
    });

    // Setup Response stuff
    var breakpointsArray = [
        0,
        breakpoints.mobile,
        breakpoints.tablet,
        breakpoints.desktop
    ];
    Response.create({ mode: 'markup', prefix: 'r', breakpoints: breakpointsArray });
    Response.create({ mode: 'src',  prefix: 'src', breakpoints: breakpointsArray });

    // Handlebars helpers

    /**
     * Helper for resolving static urls in handlebars templates
     * @param { URL to convert to static, same as argument for django static template function } url
     * @return { full static url }
     */
    Handlebars.registerHelper('static', function(url) {
        return CEL.serverVars.staticRoot + url;
    });

    /**
     * Central api for getting urls for the app
     * @param { logical name of the endpoint } name
     * @param { Options for creating the url, depends on name } opts
     * @return { URL string for request }
     */
    var getUrl = function (name, opts) {
        var url = '';
        switch (name) {
            case 'origin':
                // IE < 9 doesn't define location.origin
                return window.location.origin ||
                    (window.location.protocol + "//" + window.location.host);
            case 'location-api':
                // requires opts.locations to be comma separated string or
                //      array of integers
                url = '/' + ($.cookie('django_language') || CEL.serverVars.default_language) +
                    '/api/location/';
                if (opts && opts.locations) {
                    url += opts.locations.toString() + '/';
                }
                return url;
            case 'neighborhood-api':
                return '/' + ($.cookie('django_language') || CEL.serverVars.default_language) +
                    '/api/neighborhood/';
            case 'neighborhoods-topo':
                return '/static/js/neighborhoods-topo.json';
            case 'neighborhoods-geojson':
                return '/static/js/neighborhoods.json';
            case 'browse':
                if (!opts) {
                    return '/search/';
                }
                switch (opts.type) {
                    case 'latlng':
                        url = '/search/?lat=' + opts.lat + '&lng=' + opts.lng;
                        if (opts.zoom) {
                            url += '&zoom=' + opts.zoom;
                        }
                        return url;
                    case 'geo-latlng':
                        url = '/search/?geolat=' + opts.lat + '&geolng=' + opts.lng;
                        return url;
                    case 'neighborhood':
                        return '/search/?neighborhood=' + opts.neighborhood;
                    case 'location':
                        return '/search/?location=' + opts.location;
                    default:
                        break;
                }
                break;
            case 'single-location':
                return '/location/' + opts.location + '/';
            case 'starred':
                url = '/starred/';
                if (opts && opts.locations) {
                    url += opts.locations.toString() + '/';
                }
                return url;
            case 'icon-school':
                return '/static/img/leaflet-icons/school.png';
            case 'icon-school-accredited':
                return '/static/img/leaflet-icons/school-accredited.png';
            case 'icon-school-starred':
                return '/static/img/leaflet-icons/school-starred.png';
            case 'icon-school-accredited-starred':
                return '/static/img/leaflet-icons/school-accredited-starred.png';
            case 'icon-center':
                return '/static/img/leaflet-icons/center.png';
            case 'icon-center-accredited':
                return '/static/img/leaflet-icons/center-accredited.png';
            case 'icon-center-starred':
                return '/static/img/leaflet-icons/center-starred.png';
            case 'icon-center-accredited-starred':
                return '/static/img/leaflet-icons/center-accredited-starred.png';
            case 'icon-geolocation':
                return '/static/img/leaflet-icons/geocode.png';
            default:
                break;
        }
        throw 'Unknown URL endpoint';
    };

    // geolocation
    if ('geolocation' in navigator) {
        $(document).ready(function() {
            $('.geolocation-button').bind('click', function(e) {
                e.preventDefault();
                navigator.geolocation.getCurrentPosition(function(position) {
                    window.location.href = getUrl(
                        'browse',
                        {
                            type: 'geo-latlng',
                            lat: position.coords.latitude,
                            lng: position.coords.longitude
                        }
                    );
                }, function(e) {
                    e.preventDefault();
                    alert(gettext('Please enable geolocation services.'));
                });
            });
        });
    } else {
        $('.geolocation-button').hide();
    }

    // Set up social sharing behavior
    // The options argument must be an object that contains both a url and a title,
    // which are used to construct the appropriate sharing links.
    $('#share-modal').on('init-modal', function(e, options) {
        // a lot of these urls won't work when passing 'localhost' urls, but once we
        // move to a publically accessible url, they will work just fine
        var templates = {
                mail: 'mailto:?body={{ url }}&subject={{ title }}',
                facebook: 'http://facebook.com/sharer.php?s=100&p[url]={{ url }}&p[title]={{ title }}',
                twitter: 'https://twitter.com/share?text={{ title }}%20{{ url }}',

                // looks like google recently broke their permalink that allows adding text:
                // 'https://m.google.com/app/plus/x/?v=compose&content={{ title }}%20{{ url }}'
                // until they fix it, the following will work, but will just share the url
                gplus: 'https://plus.google.com/share?url={{ url }}'
            },
            $modal = $(this),
            $text = $('#social-text');

        // fill the input box in with the url
        $text.attr('href', options.url).text(options.url);

        // a url is generated for each template defined in 'templates'.
        // to add a new service, create a link with the id 'social-[service]',
        // and add a template to the 'templates' object.
        $.each(templates, function (service, template) {
            $('#social-' + service).attr('href', Handlebars.compile(template)(options));
        });

        // display the modal
        $modal.modal('show');
    });

    return {
        getUrl: getUrl,

        breakpoints: breakpoints,

        // Stolen from _.js v1.5.1
        // https://github.com/jashkenas/underscore/blob/dc5a3fa0133b7000c36ba76a413139c63f646789/underscore.js
        // See project root for license
        //
        // Returns a function, that, as long as it continues to be invoked, will not
        // be triggered. The function will be called after it stops being called for
        // N milliseconds. If `immediate` is passed, trigger the function on the
        // leading edge, instead of the trailing.
        debounce: function(func, wait, immediate) {
            var result,
                timeout = null;
            return function() {
                var context = this,
                    args = arguments,
                    later = function() {
                        timeout = null;
                        if (!immediate) result = func.apply(context, args);
                    },
                    callNow = immediate && !timeout;
                    clearTimeout(timeout);
                    timeout = setTimeout(later, wait);
                    if (callNow) {
                        result = func.apply(context, args);
                    }

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


<commit_before>import { isString } from 'core/util';
import { createEl } from 'core/util/dom';
import Point from 'geo/Point';
import { Geometry, Marker } from 'geometry';
import UIComponent from './UIComponent';

/**
 * @property {Object} options
 * @property {Boolean} [options.autoPan=true]  - set it to false if you don't want the map to do panning animation to fit the opened window.
 * @property {Boolean} [options.autoCloseOn=null] - Auto close infowindow on map's events, e.g. "click contextmenu" will close infowindow with click or right click on map.
 * @property {Boolean} [options.autoOpenOn=null]  - Auto open infowindow on owner's events, e.g. "click" will open infowindow with click or right click on window's owner.
 * @property {Number}  [options.width=300]     - default width
 * @property {Number}  [options.minHeight=120] - minimun height
 * @property {Boolean} [options.custom=false]  - set it to true if you want a customized infowindow, customized html codes or a HTMLElement is set to content.
 * @property {String}  [options.title=null]    - title of the infowindow.
 * @property {String|HTMLElement}  options.content - content of the infowindow.
 * @memberOf ui.InfoWindow
 * @instance
 */
const options = {
    'autoPan': true,
    'autoCloseOn' : null,
    'autoOpenOn' : 'click',
    'width': 300,
    'minHeight': 120,
    'custom': false,
    'title': null,
    'content': null
};

/**
 * @classdesc
 * Class for info window, a popup on the map to display any useful infomation you wanted.
 * @category ui
 * @extends ui.UIComponent
 * @param {Object} options - options defined in [InfoWindow]{@link InfoWindow#options}
 * @memberOf ui
 */
class InfoWindow extends UIComponent {

    // TODO: obtain class in super
    _getClassName() {
        return 'InfoWindow';
    }

    /**
     * Adds the UI Component to a geometry or a map
     * @param {Geometry|Map} owner - geometry or map to addto.
     * @returns {UIComponent} this
     * @fires UIComponent#add
     */
    addTo(owner) {
        if (owner instanceof Geometry) {
            if (owner.getInfoWindow() && owner.getInfoWindow() !== this) {
                owner.removeInfoWindow();
            }
            owner._infoWindow = this;
        }
        return super.addTo(owner);
    }

    /**
     * Set the content of the infowindow.
     * @param {String|HTMLElement} content - content of the infowindow.
     * return {InfoWindow} this
     * @fires InfoWindow#contentchange
     */
    setContent(content) {
        const old = this.options['content'];
        this.options['content'] = content;
        /**
         * contentchange event.
         *
         * @event InfoWindow#contentchange
         * @type {Object}
         * @property {String} type - contentchange
         * @property {InfoWindow} target - InfoWindow
         * @property {String|HTMLElement} old      - old content
         * @property {String|HTMLElement} new      - new content
         */
        this.fire('contentchange', {
            'old': old,
            'new': content
        });
        if (this.isVisible()) {
            this.show(this._coordinate);
        }
        return this;
    }

    /**
     * Get content of  the infowindow.
     * @return {String|HTMLElement} - content of the infowindow
     */
    getContent() {
        return this.options['content'];
    }

    /**
     * Set the title of the infowindow.
     * @param {String|HTMLElement} title - title of the infowindow.
     * return {InfoWindow} this
     * @fires InfoWindow#titlechange
     */
    setTitle(title) {
        const old = title;
        this.options['title'] = title;
        /**
         * titlechange event.
         *
         * @event InfoWindow#titlechange
         * @type {Object}
         * @property {String} type - titlechange
         * @property {InfoWindow} target - InfoWindow
         * @property {String} old      - old content
         * @property {String} new      - new content
         */
        this.fire('contentchange', {
            'old': old,
            'new': title
        });
        if (this.isVisible()) {
            this.show(this._coordinate);
        }
        return this;
    }

    /**
     * Get title of  the infowindow.
     * @return {String|HTMLElement} - content of the infowindow
     */
    getTitle() {
        return this.options['title'];
    }

    buildOn() {
        if (this.options['custom']) {
            if (isString(this.options['content'])) {
                const dom = createEl('div');
                dom.innerHTML = this.options['content'];
                return dom;
            } else {
                return this.options['content'];
            }
        }
        const dom = createEl('div');
        dom.className = 'maptalks-msgBox';
        dom.style.width = this._getWindowWidth() + 'px';
        let content = '';
        if (this.options['title']) {
            content += '
' + this.options['title'] + '
';
        }
        const onClose = '"this.parentNode.style.display=\'none\';return false;"';
        content += '
' + this.options['content'] + '
';
        dom.innerHTML = content;
        return dom;
    }

    /**
     * Gets InfoWindow's transform origin for animation transform
     * @protected
     * @return {Point} transform origin
     */
    getTransformOrigin() {
        const size = this.getSize();
        const o = new Point(size['width'] / 2, size['height']);
        if (!this.options['custom']) {
            o._add(4, 12);
        }
        return o;
    }

    getOffset() {
        const size = this.getSize();
        const o = new Point(-size['width'] / 2, -size['height']);
        if (!this.options['custom']) {
            o._sub(4, 12);
        }
        if (this.getOwner() instanceof Marker) {
            const markerSize = this.getOwner().getSize();
            if (markerSize) {
                o._add(0, -markerSize['height']);
            }
        }
        return o;
    }

    show(coordinate) {
        if (!this.getMap()) {
            return this;
        }
        if (!this.getMap().options['enableInfoWindow']) {
            return this;
        }
        return super.show(coordinate);
    }

    getEvents() {
        if (!this.options['autoCloseOn']) {
            return null;
        }
        const events = {};
        events[this.options['autoCloseOn']] = this.hide;
        return events;
    }

    getOwnerEvents() {
        const owner = this.getOwner();
        if (!this.options['autoOpenOn'] || !owner) {
            return null;
        }
        const events = {};
        events[this.options['autoOpenOn']] = this._onAutoOpen;
        return events;
    }

    _onAutoOpen(e) {
        this.show(e.coordinate);
    }

    _getWindowWidth() {
        const defaultWidth = 300;
        let width = this.options['width'];
        if (!width) {
            width = defaultWidth;
        }
        return width;
    }
}

InfoWindow.mergeOptions(options);

export default InfoWindow;
<commit_msg>fix marker's auto open position of infowindow<commit_after>import { isString } from 'core/util';
import { createEl } from 'core/util/dom';
import Point from 'geo/Point';
import { Geometry, Marker } from 'geometry';
import UIComponent from './UIComponent';

/**
 * @property {Object} options
 * @property {Boolean} [options.autoPan=true]  - set it to false if you don't want the map to do panning animation to fit the opened window.
 * @property {Boolean} [options.autoCloseOn=null] - Auto close infowindow on map's events, e.g. "click contextmenu" will close infowindow with click or right click on map.
 * @property {Boolean} [options.autoOpenOn=null]  - Auto open infowindow on owner's events, e.g. "click" will open infowindow with click or right click on window's owner.
 * @property {Number}  [options.width=300]     - default width
 * @property {Number}  [options.minHeight=120] - minimun height
 * @property {Boolean} [options.custom=false]  - set it to true if you want a customized infowindow, customized html codes or a HTMLElement is set to content.
 * @property {String}  [options.title=null]    - title of the infowindow.
 * @property {String|HTMLElement}  options.content - content of the infowindow.
 * @memberOf ui.InfoWindow
 * @instance
 */
const options = {
    'autoPan': true,
    'autoCloseOn' : null,
    'autoOpenOn' : 'click',
    'width': 300,
    'minHeight': 120,
    'custom': false,
    'title': null,
    'content': null
};

/**
 * @classdesc
 * Class for info window, a popup on the map to display any useful infomation you wanted.
 * @category ui
 * @extends ui.UIComponent
 * @param {Object} options - options defined in [InfoWindow]{@link InfoWindow#options}
 * @memberOf ui
 */
class InfoWindow extends UIComponent {

    // TODO: obtain class in super
    _getClassName() {
        return 'InfoWindow';
    }

    /**
     * Adds the UI Component to a geometry or a map
     * @param {Geometry|Map} owner - geometry or map to addto.
     * @returns {UIComponent} this
     * @fires UIComponent#add
     */
    addTo(owner) {
        if (owner instanceof Geometry) {
            if (owner.getInfoWindow() && owner.getInfoWindow() !== this) {
                owner.removeInfoWindow();
            }
            owner._infoWindow = this;
        }
        return super.addTo(owner);
    }

    /**
     * Set the content of the infowindow.
     * @param {String|HTMLElement} content - content of the infowindow.
     * return {InfoWindow} this
     * @fires InfoWindow#contentchange
     */
    setContent(content) {
        const old = this.options['content'];
        this.options['content'] = content;
        /**
         * contentchange event.
         *
         * @event InfoWindow#contentchange
         * @type {Object}
         * @property {String} type - contentchange
         * @property {InfoWindow} target - InfoWindow
         * @property {String|HTMLElement} old      - old content
         * @property {String|HTMLElement} new      - new content
         */
        this.fire('contentchange', {
            'old': old,
            'new': content
        });
        if (this.isVisible()) {
            this.show(this._coordinate);
        }
        return this;
    }

    /**
     * Get content of  the infowindow.
     * @return {String|HTMLElement} - content of the infowindow
     */
    getContent() {
        return this.options['content'];
    }

    /**
     * Set the title of the infowindow.
     * @param {String|HTMLElement} title - title of the infowindow.
     * return {InfoWindow} this
     * @fires InfoWindow#titlechange
     */
    setTitle(title) {
        const old = title;
        this.options['title'] = title;
        /**
         * titlechange event.
         *
         * @event InfoWindow#titlechange
         * @type {Object}
         * @property {String} type - titlechange
         * @property {InfoWindow} target - InfoWindow
         * @property {String} old      - old content
         * @property {String} new      - new content
         */
        this.fire('contentchange', {
            'old': old,
            'new': title
        });
        if (this.isVisible()) {
            this.show(this._coordinate);
        }
        return this;
    }

    /**
     * Get title of  the infowindow.
     * @return {String|HTMLElement} - content of the infowindow
     */
    getTitle() {
        return this.options['title'];
    }

    buildOn() {
        if (this.options['custom']) {
            if (isString(this.options['content'])) {
                const dom = createEl('div');
                dom.innerHTML = this.options['content'];
                return dom;
            } else {
                return this.options['content'];
            }
        }
        const dom = createEl('div');
        dom.className = 'maptalks-msgBox';
        dom.style.width = this._getWindowWidth() + 'px';
        let content = '';
        if (this.options['title']) {
            content += '
' + this.options['title'] + '
';
        }
        const onClose = '"this.parentNode.style.display=\'none\';return false;"';
        content += '
' + this.options['content'] + '
';
        dom.innerHTML = content;
        return dom;
    }

    /**
     * Gets InfoWindow's transform origin for animation transform
     * @protected
     * @return {Point} transform origin
     */
    getTransformOrigin() {
        const size = this.getSize();
        const o = new Point(size['width'] / 2, size['height']);
        if (!this.options['custom']) {
            o._add(4, 12);
        }
        return o;
    }

    getOffset() {
        const size = this.getSize();
        const o = new Point(-size['width'] / 2, -size['height']);
        if (!this.options['custom']) {
            o._sub(4, 12);
        }
        if (this.getOwner() instanceof Marker) {
            const markerSize = this.getOwner().getSize();
            if (markerSize) {
                o._add(0, -markerSize['height']);
            }
        }
        return o;
    }

    show(coordinate) {
        if (!this.getMap()) {
            return this;
        }
        if (!this.getMap().options['enableInfoWindow']) {
            return this;
        }
        return super.show(coordinate);
    }

    getEvents() {
        if (!this.options['autoCloseOn']) {
            return null;
        }
        const events = {};
        events[this.options['autoCloseOn']] = this.hide;
        return events;
    }

    getOwnerEvents() {
        const owner = this.getOwner();
        if (!this.options['autoOpenOn'] || !owner) {
            return null;
        }
        const events = {};
        events[this.options['autoOpenOn']] = this._onAutoOpen;
        return events;
    }

    _onAutoOpen(e) {
        const owner = this.getOwner();
        if (owner instanceof Marker) {
            this.show();
        } else {
            this.show(e.coordinate);
        }
    }

    _getWindowWidth() {
        const defaultWidth = 300;
        let width = this.options['width'];
        if (!width) {
            width = defaultWidth;
        }
        return width;
    }
}

InfoWindow.mergeOptions(options);

export default InfoWindow;
<|endoftext|>