adobe/brackets

View on GitHub
src/LiveDevelopment/main.js

Summary

Maintainability
D
2 days
Test Coverage
/*
 * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*global less */

/**
 * main integrates LiveDevelopment into Brackets
 *
 * This module creates two menu items:
 *
 *  "Go Live": open or close a Live Development session and visualize the status
 *  "Highlight": toggle source highlighting
 */
define(function main(require, exports, module) {
    "use strict";

    var DocumentManager     = require("document/DocumentManager"),
        Commands            = require("command/Commands"),
        AppInit             = require("utils/AppInit"),
        LiveDevelopment     = require("LiveDevelopment/LiveDevelopment"),
        MultiBrowserLiveDev = require("LiveDevelopment/LiveDevMultiBrowser"),
        Inspector           = require("LiveDevelopment/Inspector/Inspector"),
        CommandManager      = require("command/CommandManager"),
        PreferencesManager  = require("preferences/PreferencesManager"),
        Dialogs             = require("widgets/Dialogs"),
        DefaultDialogs      = require("widgets/DefaultDialogs"),
        UrlParams           = require("utils/UrlParams").UrlParams,
        Strings             = require("strings"),
        ExtensionUtils      = require("utils/ExtensionUtils"),
        StringUtils         = require("utils/StringUtils");

    var params = new UrlParams();
    var config = {
        experimental: false, // enable experimental features
        debug: true, // enable debug output and helpers
        autoconnect: false, // go live automatically after startup?
        highlight: true, // enable highlighting?
        highlightConfig: { // the highlight configuration for the Inspector
            borderColor:  {r: 255, g: 229, b: 153, a: 0.66},
            contentColor: {r: 111, g: 168, b: 220, a: 0.55},
            marginColor:  {r: 246, g: 178, b: 107, a: 0.66},
            paddingColor: {r: 147, g: 196, b: 125, a: 0.66},
            showInfo: true
        }
    };
    // Status labels/styles are ordered: error, not connected, progress1, progress2, connected.
    var _status,
        _allStatusStyles = ["warning", "info", "success", "out-of-sync", "sync-error"].join(" ");

    var _$btnGoLive; // reference to the GoLive button

    // current selected implementation (LiveDevelopment | LiveDevMultiBrowser)
    var LiveDevImpl;

    // "livedev.multibrowser" preference
    var PREF_MULTIBROWSER = "multibrowser";
    var prefs = PreferencesManager.getExtensionPrefs("livedev");
    var multiBrowserPref = prefs.definePreference(PREF_MULTIBROWSER, "boolean", false, {
        description: Strings.DESCRIPTION_LIVE_DEV_MULTIBROWSER
    });

    // "livedev.remoteHighlight" preference
    var PREF_REMOTEHIGHLIGHT = "remoteHighlight";
    var remoteHighlightPref = prefs.definePreference(PREF_REMOTEHIGHLIGHT, "object", {
        animateStartValue: {
            "background-color": "rgba(0, 162, 255, 0.5)",
            "opacity": 0
        },
        animateEndValue: {
            "background-color": "rgba(0, 162, 255, 0)",
            "opacity": 0.6
        },
        "paddingStyling": {
            "border-width": "1px",
            "border-style": "dashed",
            "border-color": "rgba(0, 162, 255, 0.5)"
        },
        "marginStyling": {
            "background-color": "rgba(21, 165, 255, 0.58)"
        },
        "borderColor": "rgba(21, 165, 255, 0.85)",
        "showPaddingMargin": true
    }, {
        description: Strings.DESCRIPTION_LIVE_DEV_HIGHLIGHT_SETTINGS
    });
    
    /** Toggles or sets the preference **/
    function _togglePref(key, value) {
        var val,
            oldPref = !!prefs.get(key);

        if (value === undefined) {
            val = !oldPref;
        } else {
            val = !!value;
        }

        // update menu
        if (val !== oldPref) {
            prefs.set(key, val);
        }

        return val;
    }

    /* Toggles or sets the "livedev.multibrowser" preference */
    function _toggleLivePreviewMultiBrowser(value) {
        var val = _togglePref(PREF_MULTIBROWSER, value);

        CommandManager.get(Commands.TOGGLE_LIVE_PREVIEW_MB_MODE).setChecked(val);
        // Issue #10217: multi-browser does not support user server, so disable
        // the setting if it is enabled.
        CommandManager.get(Commands.FILE_PROJECT_SETTINGS).setEnabled(!val);
    }

    /** Load Live Development LESS Style */
    function _loadStyles() {
        var lessText = require("text!LiveDevelopment/main.less");

        less.render(lessText, function onParse(err, tree) {
            console.assert(!err, err);
            ExtensionUtils.addEmbeddedStyleSheet(tree.css);
        });
    }

    /**
     * Change the appearance of a button. Omit text to remove any extra text; omit style to return to default styling;
     * omit tooltip to leave tooltip unchanged.
     */
    function _setLabel($btn, text, style, tooltip) {
        // Clear text/styles from previous status
        $("span", $btn).remove();
        $btn.removeClass(_allStatusStyles);

        // Set text/styles for new status
        if (text && text.length > 0) {
            $("<span class=\"label\">")
                .addClass(style)
                .text(text)
                .appendTo($btn);
        } else {
            $btn.addClass(style);
        }

        if (tooltip) {
            $btn.attr("title", tooltip);
        }
    }

    /**
     * Toggles LiveDevelopment and synchronizes the state of UI elements that reports LiveDevelopment status
     *
     * Stop Live Dev when in an active state (ACTIVE, OUT_OF_SYNC, SYNC_ERROR).
     * Start Live Dev when in an inactive state (ERROR, INACTIVE).
     * Do nothing when in a connecting state (CONNECTING, LOADING_AGENTS).
     */
    function _handleGoLiveCommand() {
        if (LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) {
            LiveDevImpl.close();
        } else if (LiveDevImpl.status <= LiveDevImpl.STATUS_INACTIVE) {
            if (!params.get("skipLiveDevelopmentInfo") && !PreferencesManager.getViewState("livedev.afterFirstLaunch")) {
                PreferencesManager.setViewState("livedev.afterFirstLaunch", "true");
                Dialogs.showModalDialog(
                    DefaultDialogs.DIALOG_ID_INFO,
                    Strings.LIVE_DEVELOPMENT_INFO_TITLE,
                    Strings.LIVE_DEVELOPMENT_INFO_MESSAGE
                ).done(function (id) {
                    LiveDevImpl.open();
                });
            } else {
                LiveDevImpl.open();
            }
        }
    }

    /** Called on status change */
    function _showStatusChangeReason(reason) {
        // Destroy the previous twipsy (options are not updated otherwise)
        _$btnGoLive.twipsy("hide").removeData("twipsy");

        // If there was no reason or the action was an explicit request by the user, don't show a twipsy
        if (!reason || reason === "explicit_close") {
            return;
        }

        // Translate the reason
        var translatedReason = Strings["LIVE_DEV_" + reason.toUpperCase()];
        if (!translatedReason) {
            translatedReason = StringUtils.format(Strings.LIVE_DEV_CLOSED_UNKNOWN_REASON, reason);
        }

        // Configure the twipsy
        var options = {
            placement: "left",
            trigger: "manual",
            autoHideDelay: 5000,
            title: function () {
                return translatedReason;
            }
        };

        // Show the twipsy with the explanation
        _$btnGoLive.twipsy(options).twipsy("show");
    }

    /** Create the menu item "Go Live" */
    function _setupGoLiveButton() {
        if (!_$btnGoLive) {
            _$btnGoLive = $("#toolbar-go-live");
            _$btnGoLive.click(function onGoLive() {
                _handleGoLiveCommand();
            });
        }
        LiveDevImpl.on("statusChange", function statusChange(event, status, reason) {
            // status starts at -1 (error), so add one when looking up name and style
            // See the comments at the top of LiveDevelopment.js for details on the
            // various status codes.
            _setLabel(_$btnGoLive, null, _status[status + 1].style, _status[status + 1].tooltip);
            _showStatusChangeReason(reason);
            if (config.autoconnect) {
                window.sessionStorage.setItem("live.enabled", status === 3);
            }
        });

        // Initialize tooltip for 'not connected' state
        _setLabel(_$btnGoLive, null, _status[1].style, _status[1].tooltip);
    }

    /** Maintains state of the Live Preview menu item */
    function _setupGoLiveMenu() {
        LiveDevImpl.on("statusChange", function statusChange(event, status) {
            // Update the checkmark next to 'Live Preview' menu item
            // Add checkmark when status is STATUS_ACTIVE; otherwise remove it
            CommandManager.get(Commands.FILE_LIVE_FILE_PREVIEW).setChecked(status === LiveDevImpl.STATUS_ACTIVE);
            CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(status === LiveDevImpl.STATUS_ACTIVE);
        });
    }

    function _updateHighlightCheckmark() {
        CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setChecked(config.highlight);
    }

    function _handlePreviewHighlightCommand() {
        config.highlight = !config.highlight;
        _updateHighlightCheckmark();
        if (config.highlight) {
            LiveDevImpl.showHighlight();
        } else {
            LiveDevImpl.hideHighlight();
        }
        PreferencesManager.setViewState("livedev.highlight", config.highlight);
    }

    /**
     * Sets the MultiBrowserLiveDev implementation if multibrowser is truthy,
     * keeps default LiveDevelopment implementation based on CDT otherwise.
     * It also resets the listeners and UI elements.
     */
    function _setImplementation(multibrowser) {
        if (multibrowser) {
            // set implemenation
            LiveDevImpl = MultiBrowserLiveDev;
            // update styles for UI status
            _status = [
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "warning" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_CONNECTED, style: "success" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC, style: "out-of-sync" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR, style: "sync-error" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" }
            ];
        } else {
            LiveDevImpl = LiveDevelopment;
            _status = [
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "warning" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS2, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_CONNECTED, style: "success" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC, style: "out-of-sync" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR, style: "sync-error" }
            ];
        }
        // setup status changes listeners for new implementation
        _setupGoLiveButton();
        _setupGoLiveMenu();
        // toggle the menu
        _toggleLivePreviewMultiBrowser(multibrowser);
    }

    /** Setup window references to useful LiveDevelopment modules */
    function _setupDebugHelpers() {
        window.ld = LiveDevelopment;
        window.i = Inspector;
        window.report = function report(params) { window.params = params; console.info(params); };
    }

    /** force reload the live preview */
    function _handleReloadLivePreviewCommand() {
        if (LiveDevelopment.status >= LiveDevelopment.STATUS_ACTIVE) {
            LiveDevelopment.reload();
        }
    }

    /** Initialize LiveDevelopment */
    AppInit.appReady(function () {
        params.parse();
        config.remoteHighlight = prefs.get(PREF_REMOTEHIGHLIGHT);

        Inspector.init(config);
        LiveDevelopment.init(config);

        // init experimental multi-browser implementation
        // it can be enable by setting 'livedev.multibrowser' preference to true.
        // It has to be initiated at this point in case of dynamically switching
        // by changing the preference value.
        MultiBrowserLiveDev.init(config);

        _loadStyles();
        _updateHighlightCheckmark();

        _setImplementation(prefs.get(PREF_MULTIBROWSER));

        if (config.debug) {
            _setupDebugHelpers();
        }

        // trigger autoconnect
        if (config.autoconnect &&
                window.sessionStorage.getItem("live.enabled") === "true" &&
                DocumentManager.getCurrentDocument()) {
            _handleGoLiveCommand();
        }

        // Redraw highlights when window gets focus. This ensures that the highlights
        // will be in sync with any DOM changes that may have occurred.
        $(window).focus(function () {
            if (Inspector.connected() && config.highlight) {
                LiveDevelopment.redrawHighlight();
            }
        });

        multiBrowserPref
            .on("change", function () {
                // Stop the current session if it is open and set implementation based on
                // the pref value. We could start the new implementation immediately, but
                // since the current document is potentially a user preferences file, Live
                // Preview will not locate the html file to serve.
                if (LiveDevImpl && LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) {
                    LiveDevImpl.close()
                        .done(function () {
                            // status changes will now be listened by the new implementation
                            LiveDevImpl.off("statusChange");
                            _setImplementation(prefs.get(PREF_MULTIBROWSER));
                        });
                } else {
                    _setImplementation(prefs.get(PREF_MULTIBROWSER));
                }
            });
        
        remoteHighlightPref
            .on("change", function () {
                config.remoteHighlight = prefs.get(PREF_REMOTEHIGHLIGHT);
                       
                if (LiveDevImpl && LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) {
                    LiveDevImpl.agents.remote.call("updateConfig",JSON.stringify(config));
                }
            });

    });

    // init prefs
    PreferencesManager.stateManager.definePreference("livedev.highlight", "boolean", true)
        .on("change", function () {
            config.highlight = PreferencesManager.getViewState("livedev.highlight");
            _updateHighlightCheckmark();
        });

    config.highlight = PreferencesManager.getViewState("livedev.highlight");

    // init commands
    CommandManager.register(Strings.CMD_LIVE_FILE_PREVIEW,  Commands.FILE_LIVE_FILE_PREVIEW, _handleGoLiveCommand);
    CommandManager.register(Strings.CMD_LIVE_HIGHLIGHT, Commands.FILE_LIVE_HIGHLIGHT, _handlePreviewHighlightCommand);
    CommandManager.register(Strings.CMD_RELOAD_LIVE_PREVIEW, Commands.CMD_RELOAD_LIVE_PREVIEW, _handleReloadLivePreviewCommand);
    CommandManager.register(Strings.CMD_TOGGLE_LIVE_PREVIEW_MB_MODE, Commands.TOGGLE_LIVE_PREVIEW_MB_MODE, _toggleLivePreviewMultiBrowser);

    CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(false);

    // Export public functions
});