<commit_before>/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, CodeMirror: false */

/**
 * EditorManager owns the UI for the editor area. This essentially mirrors the 'current document'
 * property maintained by DocumentManager's model.
 *
 * Note that there is a little bit of unusual overlap between EditorManager and DocumentManager:
 * because the Document state is actually stored in the CodeMirror editor UI, DocumentManager is
 * not a pure headless model. Each Document encapsulates an editor instance, and thus EditorManager
 * must have some knowledge about Document's internal state (we access its _editor property).
 *
 * This module does not dispatch any events.
 */
define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var FileUtils           = require("FileUtils"),
        DocumentManager     = require("DocumentManager"),
        Editor              = require("Editor").Editor,
        EditorUtils         = require("EditorUtils"),
        Strings             = require("strings");

    /** @type {jQueryObject} DOM node that contains all editors (visible and hidden alike) */
    var _editorHolder = null;

    /** @type {Editor} */
    var _currentEditor = null;
    /** @type {Document} */
    var _currentEditorsDocument = null;

    /** @type {number} Used by {@link #_updateEditorSize()} */
    var _resizeTimeout = null;

    /**
     * Registered inline-editor widget providers. See {@link #registerInlineEditProvider()}.
     * @type {Array.<function(...)>}
     */
    var _inlineEditProviders = [];

    /**
     * Creates a new CodeMirror editor instance containing the given text. The editor's mode is set
     * based on the given filename's extension (the actual file on disk is never examined). The
     * editor is appended to the given container as a visible child.
     * @param {!string} text  The text content of the editor.
     * @param {!string} fileNameToSelectMode  A filename from which to infer the editor's mode. May
     *          include path too.
     * @param {!jQueryObject} container  Container to add the editor to.
     * @param {!function} onInlineGesture  Handler for Ctrl+E command (open/close inline, depending
                on context)
     * @return {Editor} the newly created editor.
     */
    function _createEditorFromText(text, fileNameToSelectMode, container, onInlineGesture) {
        var mode = EditorUtils.getModeFromFileExtension(fileNameToSelectMode);

        var extraKeys = {
            "Ctrl-E" : function (editor) {
                onInlineGesture(editor);
            },
            "Cmd-E" : function (editor) {
                onInlineGesture(editor);
            }
        };

        return new Editor(text, mode, container, extraKeys);
    }

    /** Bound to Ctrl+E on outermost editors */
    function _openInlineWidget(editor) {
        // Run through inline-editor providers until one responds
        var pos = editor.getCursorPos();
        var inlinePromise;
        var i;
        for (i = 0; i < _inlineEditProviders.length && !inlinePromise; i++) {
            var provider = _inlineEditProviders[i];
            inlinePromise = provider(editor, pos);
        }

        // If one of them will provide a widget, show it inline once ready
        if (inlinePromise) {
            inlinePromise.done(function (inlineContent) {
                var inlineId = editor.addInlineWidget(pos, inlineContent.content, inlineContent.height, inlineContent);
                inlineContent.onAdded(inlineId);
            });
        }
    }

    function _closeInlineWidget(hostEditor, inlineId) {
        // Place cursor back on the line just above the inline (the line from which it was opened)
        // If cursor's already on that line, leave it be to preserve column position
        var widgetLine = hostEditor._codeMirror.getInlineWidgetInfo(inlineId).line;
        var cursorLine = hostEditor.getCursorPos().line;
        if (cursorLine !== widgetLine) {
            hostEditor.setCursorPos({ line: widgetLine, pos: 0 });
        }

        hostEditor.removeInlineWidget(inlineId);

        hostEditor.focus();
    }

    function registerInlineEditProvider(provider) {
        _inlineEditProviders.push(provider);
    }

    /**
     * @private
     * Given a host editor, return a list of all its open inline editors. (Ignoring any other
     * inline widgets that might be open).
     * @param {!Editor} hostEditor
     */
    function _getInlineEditors(hostEditor) {
        var inlineEditors = [];
        hostEditor.getInlineWidgets().forEach(function (widget) {
            if (widget.data.editor) {
                inlineEditors.push(widget.data.editor);
            }
        });
        return inlineEditors;
    }

    /**
     * @private
     * Given a host editor and its inline editors, find the widest gutter and make all the others match
     * @param {!Editor} hostEditor Host editor containing all the inline editors to sync
     */
    function _syncGutterWidths(hostEditor) {
        var editors = _getInlineEditors(hostEditor);
        // add the host to the list and go through them all
        editors.push(hostEditor);

        var maxWidth = 0;
        editors.forEach(function (editor) {
            var gutter = $(editor._codeMirror.getGutterElement());
            gutter.css("min-width", "");
            var curWidth = gutter.width();
            if (curWidth > maxWidth) {
                maxWidth = curWidth;
            }
        });

        if (editors.length === 1) {
            //There's only the host, just bail
            editors[0]._codeMirror.setOption("gutter", true);
            return;
        }

        maxWidth = maxWidth + "px";
        editors.forEach(function (editor) {
            $(editor._codeMirror.getGutterElement()).css("min-width", maxWidth);
            editor._codeMirror.setOption("gutter", true);
        });
    }

    /**
     * Creates a new "full-size" (not inline) Editor from the Document's file, and sets it as the
     * Document's main editor. The editor is not yet visible; to show it, call
     * DocumentManager.showInEditor().
     * @param {!Document} document  Document whose main/full Editor to create
     * @return {Deferred} a jQuery Deferred that will be resolved once Document.editor is populated,
     *      or rejected with a FileError if the file cannot be read. Does not show any error UI.
     */
    function createFullEditorForDocument(document) {
        var result = new $.Deferred(),
            reader = FileUtils.readAsText(document.file);

        reader.done(function (text, readTimestamp) {
            // Create editor; make it initially invisible
            var container = _editorHolder.get(0);
            var editor = _createEditorFromText(text, document.file.fullPath, container, _openInlineWidget);
            $(editor._codeMirror.getWrapperElement()).css("display", "none");

            document._setEditor(editor, readTimestamp, text);
            result.resolve();
        });
        reader.fail(function (error) {
            result.reject(error);
        });

        return result;
    }

    /**
     * Creates a new inline CodeMirror editor instance containing the given text. The editor's mode
     * is set based on the given filename's extension (the actual file on disk is never examined).
     * The editor is not yet visible.
     * @param {!Editor} hostEditor  Outer Editor instance that inline editor will sit within.
     * @param {!string} text  The text content of the editor.
     * @param {?{startLine:Number, endLine:Number}} range  If specified, all lines outside the given
     *      range are hidden from the editor. Range is inclusive. Line numbers start at 0.
     * @param {!FileEntry} sourceFile  The file from which the text was drawn. Important, since this
     *      is what ties the inline editor back to a full editor and where any edits will be saved.
     *      infer the editor's mode.
     */
    function createInlineEditorFromText(hostEditor, text, range, sourceFile) {
        // Container to hold editor & render its stylized frame
        var inlineContent = document.createElement('div');
        $(inlineContent).addClass("inlineCodeEditor");

        var myInlineId;  // id is set when afterAdded() runs
        function closeThisInline() {
            _closeInlineWidget(hostEditor, myInlineId);
            _syncGutterWidths(hostEditor);
        }

        var inlineEditor = _createEditorFromText(text, sourceFile.fullPath, inlineContent, closeThisInline);
        var inlineEditorCM = inlineEditor._codeMirror;

        // Wire up to Document and its main full-size editor
        $(inlineEditor).on("change", function () {
            // Add to working set, since it's about to become dirty
            var doc = DocumentManager.getDocumentForFile(sourceFile);
            if (!doc) {
                // File wasn't open before, so we must create a new document for it
                doc = new DocumentManager.Document(sourceFile);

                DocumentManager.addToWorkingSet(doc);
            }

            // Sync the change into the Document's full editor
            if (doc.editor) {
                doc.editor.syncFrom(inlineEditor);
            } else {
                var editorResult = createFullEditorForDocument(doc);

                editorResult.done(function () {
                    // Begin syncing from inline to full editor
                    doc.editor.syncFrom(inlineEditor);
                });
                editorResult.fail(function (error) {
                    FileUtils.showFileOpenError(fileError.code, document.file.fullPath).done(function () {
                        closeThisInline();
                    });
                });
            }
        });

        // Some tasks have to wait until we've been parented into the outer editor
        function afterAdded(inlineId) {
            myInlineId = inlineId;

            // Hide all lines other than those we want to show. We do this rather than trimming the
            // text itself so that the editor still shows accurate line numbers.
            var hidLines = false;
            if (range) {
                inlineEditorCM.operation(function () {
                    var i;
                    for (i = 0; i < range.startLine; i++) {
                        hidLines = true;
                        inlineEditorCM.hideLine(i);
                    }
                    var lineCount = inlineEditorCM.lineCount();
                    for (i = range.endLine + 1; i < lineCount; i++) {
                        hidLines = true;
                        inlineEditorCM.hideLine(i);
                    }
                });
                inlineEditor.setCursorPos(range.startLine, 0);
                _syncGutterWidths(hostEditor);
            }

            // If we haven't hidden any lines (which would have caused an update already),
            // force the editor to update its display so we measure the correct height below
            // in totalHeight().
            if (!hidLines) {
                inlineEditorCM.refresh();
            }

            // Auto-size editor to its remaining content
            var widgetHeight = inlineEditorCM.totalHeight(true);

            hostEditor._codeMirror.setInlineWidgetHeight(inlineId, widgetHeight, true);
            $(inlineEditorCM.getScrollerElement()).height(widgetHeight);
            inlineEditorCM.refresh();

            inlineEditor.focus();
        }

        return { content: inlineContent, editor: inlineEditor, height: 0, onAdded: afterAdded };
    }

    /**
     * Disposes the given document's editor if the doc is no longer "open" in the UI (visible or in
     * the working set). Otherwise does nothing.
     * @param {!Document} document
     */
    function _destroyEditorIfUnneeded(document) {
        var editor = document.editor;

        if (!editor) {
            return;
        }

        // If outgoing editor is no longer needed, dispose it
        if (!DocumentManager.getDocumentForFile(document.file)) {

            // Destroy the editor widget: CodeMirror docs for getWrapperElement() say all you have to do
            // is "Remove this from your tree to delete an editor instance."
            $(editor._codeMirror.getWrapperElement()).remove();

            // Our callers should really ensure this, but just for safety...
            if (_currentEditor === editor) {
                _currentEditorsDocument = null;
                _currentEditor = null;
            }
        }
    }

    /** Focus the currently visible editor. If no editor visible, does nothing. */
    function focusEditor() {
        if (_currentEditor) {
            _currentEditor.focus();
        }
    }

    /**
     * Resize the editor. This should only be called if the contents of the editor holder are changed
     * or if the height of the editor holder changes (except for overall window resizes, which are
     * already taken care of automatically).
     * @see #_updateEditorSize()
     */
    function resizeEditor() {
        if (_currentEditor) {
            $(_currentEditor._codeMirror.getScrollerElement()).height(_editorHolder.height());
            _currentEditor._codeMirror.refresh();
        }
    }

    /**
     * NJ's editor-resizing fix. Whenever the window resizes, we immediately adjust the editor's
     * height.
     * @see #resizeEditor()
     */
    function _updateEditorSize() {
        // The editor itself will call refresh() when it gets the window resize event.
        if (_currentEditor) {
            $(_currentEditor._codeMirror.getScrollerElement()).height(_editorHolder.height());
        }
    }

    /**
     * @private
     */
    function _doShow(document) {
        // Show new editor
        _currentEditorsDocument = document;
        _currentEditor = document.editor;

        $(_currentEditor._codeMirror.getWrapperElement()).css("display", "");

        // Window may have been resized since last time editor was visible, so kick it now
        resizeEditor();
    }

    /**
     * Make the given document's editor visible in the UI, hiding whatever was
     * visible before. Creates a new editor if none is assigned.
     * @param {!Document} document
     */
    function _showEditor(document) {
        // Hide whatever was visible before
        if (!_currentEditor) {
            $("#notEditor").css("display", "none");
        } else {
            $(_currentEditor._codeMirror.getWrapperElement()).css("display", "none");
            _destroyEditorIfUnneeded(_currentEditorsDocument);
        }

        // DocumentManager should have already ensured that we've created an Editor
        if (!document.editor) {
            throw new Error("Trying to show a currentDocument without an Editor!");
        }

        _doShow(document);
    }

    /** Hide the currently visible editor and show a placeholder UI in its place */
    function _showNoEditor() {
        if (_currentEditor) {
            $(_currentEditor._codeMirror.getWrapperElement()).css("display", "none");
            _destroyEditorIfUnneeded(_currentEditorsDocument);

            _currentEditorsDocument = null;
            _currentEditor = null;

            $("#notEditor").css("display", "");
        }
    }

    /** Handles changes to DocumentManager.getCurrentDocument() */
    function _onCurrentDocumentChange() {
        var doc = DocumentManager.getCurrentDocument();

        // Update the UI to show the right editor (or nothing), and also dispose old editor if no
        // longer needed.
        if (doc) {
            _showEditor(doc);
        } else {
            _showNoEditor();
        }
    }

    /** Handles removals from DocumentManager's working set list */
    function _onWorkingSetRemove(event, removedDoc) {
        // There's one case where an editor should be disposed even though the current document
        // didn't change: removing a document from the working set (via the "X" button). (This may
        // also cover the case where the document WAS current, if the editor-swap happens before the
        // removal from the working set.
        _destroyEditorIfUnneeded(removedDoc);
    }
    // Note: there are several paths that can lead to an editor getting destroyed
    //  - file was in working set, but not open; then closed (via working set "X" button)
    //      --> handled by _onWorkingSetRemove()
    //  - file was open, but not in working set; then navigated away from
    //      --> handled by _onCurrentDocumentChange()
    //  - file was open, but not in working set; then closed (via File > Close) (and thus implicitly
    //    navigated away from)
    //      --> handled by _onCurrentDocumentChange()
    //  - file was open AND in working set; then closed (via File > Close OR working set "X" button)
    //    (and thus implicitly navigated away from)
    //      --> handled by _onWorkingSetRemove() currently, but could be _onCurrentDocumentChange()
    //      just as easily (depends on the order of events coming from DocumentManager)

    /**
     * Designates the DOM node that will contain the currently active editor instance. EditorManager
     * will own the content of this DOM node.
     * @param {!jQueryObject} holder
     */
    function setEditorHolder(holder) {
        if (_currentEditor) {
            throw new Error("Cannot change editor area after an editor has already been created!");
        }

        _editorHolder = holder;
    }

    // Initialize: register listeners
    $(DocumentManager).on("currentDocumentChange", _onCurrentDocumentChange);
    $(DocumentManager).on("workingSetRemove", _onWorkingSetRemove);
    // Add this as a capture handler so we're guaranteed to run it before the editor does its own
    // refresh on resize.
    window.addEventListener("resize", _updateEditorSize, true);

    // Define public API
    exports.setEditorHolder = setEditorHolder;
    exports.createFullEditorForDocument = createFullEditorForDocument;
    exports.createInlineEditorFromText = createInlineEditorFromText;
    exports.focusEditor = focusEditor;
    exports.resizeEditor = resizeEditor;
    exports.registerInlineEditProvider = registerInlineEditProvider;
});
<commit_msg>Small cleanup: explicit addToWorkingSet() not needed now that we're syncing changes, since changing the main editor will trigger addition to set anyway.<commit_after>/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, CodeMirror: false */

/**
 * EditorManager owns the UI for the editor area. This essentially mirrors the 'current document'
 * property maintained by DocumentManager's model.
 *
 * Note that there is a little bit of unusual overlap between EditorManager and DocumentManager:
 * because the Document state is actually stored in the CodeMirror editor UI, DocumentManager is
 * not a pure headless model. Each Document encapsulates an editor instance, and thus EditorManager
 * must have some knowledge about Document's internal state (we access its _editor property).
 *
 * This module does not dispatch any events.
 */
define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var FileUtils           = require("FileUtils"),
        DocumentManager     = require("DocumentManager"),
        Editor              = require("Editor").Editor,
        EditorUtils         = require("EditorUtils"),
        Strings             = require("strings");

    /** @type {jQueryObject} DOM node that contains all editors (visible and hidden alike) */
    var _editorHolder = null;

    /** @type {Editor} */
    var _currentEditor = null;
    /** @type {Document} */
    var _currentEditorsDocument = null;

    /** @type {number} Used by {@link #_updateEditorSize()} */
    var _resizeTimeout = null;

    /**
     * Registered inline-editor widget providers. See {@link #registerInlineEditProvider()}.
     * @type {Array.<function(...)>}
     */
    var _inlineEditProviders = [];

    /**
     * Creates a new CodeMirror editor instance containing the given text. The editor's mode is set
     * based on the given filename's extension (the actual file on disk is never examined). The
     * editor is appended to the given container as a visible child.
     * @param {!string} text  The text content of the editor.
     * @param {!string} fileNameToSelectMode  A filename from which to infer the editor's mode. May
     *          include path too.
     * @param {!jQueryObject} container  Container to add the editor to.
     * @param {!function} onInlineGesture  Handler for Ctrl+E command (open/close inline, depending
                on context)
     * @return {Editor} the newly created editor.
     */
    function _createEditorFromText(text, fileNameToSelectMode, container, onInlineGesture) {
        var mode = EditorUtils.getModeFromFileExtension(fileNameToSelectMode);

        var extraKeys = {
            "Ctrl-E" : function (editor) {
                onInlineGesture(editor);
            },
            "Cmd-E" : function (editor) {
                onInlineGesture(editor);
            }
        };

        return new Editor(text, mode, container, extraKeys);
    }

    /** Bound to Ctrl+E on outermost editors */
    function _openInlineWidget(editor) {
        // Run through inline-editor providers until one responds
        var pos = editor.getCursorPos();
        var inlinePromise;
        var i;
        for (i = 0; i < _inlineEditProviders.length && !inlinePromise; i++) {
            var provider = _inlineEditProviders[i];
            inlinePromise = provider(editor, pos);
        }

        // If one of them will provide a widget, show it inline once ready
        if (inlinePromise) {
            inlinePromise.done(function (inlineContent) {
                var inlineId = editor.addInlineWidget(pos, inlineContent.content, inlineContent.height, inlineContent);
                inlineContent.onAdded(inlineId);
            });
        }
    }

    function _closeInlineWidget(hostEditor, inlineId) {
        // Place cursor back on the line just above the inline (the line from which it was opened)
        // If cursor's already on that line, leave it be to preserve column position
        var widgetLine = hostEditor._codeMirror.getInlineWidgetInfo(inlineId).line;
        var cursorLine = hostEditor.getCursorPos().line;
        if (cursorLine !== widgetLine) {
            hostEditor.setCursorPos({ line: widgetLine, pos: 0 });
        }

        hostEditor.removeInlineWidget(inlineId);

        hostEditor.focus();
    }

    function registerInlineEditProvider(provider) {
        _inlineEditProviders.push(provider);
    }

    /**
     * @private
     * Given a host editor, return a list of all its open inline editors. (Ignoring any other
     * inline widgets that might be open).
     * @param {!Editor} hostEditor
     */
    function _getInlineEditors(hostEditor) {
        var inlineEditors = [];
        hostEditor.getInlineWidgets().forEach(function (widget) {
            if (widget.data.editor) {
                inlineEditors.push(widget.data.editor);
            }
        });
        return inlineEditors;
    }

    /**
     * @private
     * Given a host editor and its inline editors, find the widest gutter and make all the others match
     * @param {!Editor} hostEditor Host editor containing all the inline editors to sync
     */
    function _syncGutterWidths(hostEditor) {
        var editors = _getInlineEditors(hostEditor);
        // add the host to the list and go through them all
        editors.push(hostEditor);

        var maxWidth = 0;
        editors.forEach(function (editor) {
            var gutter = $(editor._codeMirror.getGutterElement());
            gutter.css("min-width", "");
            var curWidth = gutter.width();
            if (curWidth > maxWidth) {
                maxWidth = curWidth;
            }
        });

        if (editors.length === 1) {
            //There's only the host, just bail
            editors[0]._codeMirror.setOption("gutter", true);
            return;
        }

        maxWidth = maxWidth + "px";
        editors.forEach(function (editor) {
            $(editor._codeMirror.getGutterElement()).css("min-width", maxWidth);
            editor._codeMirror.setOption("gutter", true);
        });
    }

    /**
     * Creates a new "full-size" (not inline) Editor from the Document's file, and sets it as the
     * Document's main editor. The editor is not yet visible; to show it, call
     * DocumentManager.showInEditor().
     * @param {!Document} document  Document whose main/full Editor to create
     * @return {Deferred} a jQuery Deferred that will be resolved once Document.editor is populated,
     *      or rejected with a FileError if the file cannot be read. Does not show any error UI.
     */
    function createFullEditorForDocument(document) {
        var result = new $.Deferred(),
            reader = FileUtils.readAsText(document.file);

        reader.done(function (text, readTimestamp) {
            // Create editor; make it initially invisible
            var container = _editorHolder.get(0);
            var editor = _createEditorFromText(text, document.file.fullPath, container, _openInlineWidget);
            $(editor._codeMirror.getWrapperElement()).css("display", "none");

            document._setEditor(editor, readTimestamp, text);
            result.resolve();
        });
        reader.fail(function (error) {
            result.reject(error);
        });

        return result;
    }

    /**
     * Creates a new inline CodeMirror editor instance containing the given text. The editor's mode
     * is set based on the given filename's extension (the actual file on disk is never examined).
     * The editor is not yet visible.
     * @param {!Editor} hostEditor  Outer Editor instance that inline editor will sit within.
     * @param {!string} text  The text content of the editor.
     * @param {?{startLine:Number, endLine:Number}} range  If specified, all lines outside the given
     *      range are hidden from the editor. Range is inclusive. Line numbers start at 0.
     * @param {!FileEntry} sourceFile  The file from which the text was drawn. Important, since this
     *      is what ties the inline editor back to a full editor and where any edits will be saved.
     *      infer the editor's mode.
     */
    function createInlineEditorFromText(hostEditor, text, range, sourceFile) {
        // Container to hold editor & render its stylized frame
        var inlineContent = document.createElement('div');
        $(inlineContent).addClass("inlineCodeEditor");

        var myInlineId;  // id is set when afterAdded() runs
        function closeThisInline() {
            _closeInlineWidget(hostEditor, myInlineId);
            _syncGutterWidths(hostEditor);
        }

        var inlineEditor = _createEditorFromText(text, sourceFile.fullPath, inlineContent, closeThisInline);
        var inlineEditorCM = inlineEditor._codeMirror;

        // Wire up to Document and its main full-size editor
        $(inlineEditor).on("change", function () {
            // Ensure there's a Document
            var doc = DocumentManager.getDocumentForFile(sourceFile);
            if (!doc) {
                // File wasn't open before, so we must create a new document for it
                doc = new DocumentManager.Document(sourceFile);
            }

            // Sync the change into the Document's full editor
            if (doc.editor) {
                doc.editor.syncFrom(inlineEditor);
            } else {
                var editorResult = createFullEditorForDocument(doc);

                editorResult.done(function () {
                    // Begin syncing from inline to full editor
                    doc.editor.syncFrom(inlineEditor);
                });
                editorResult.fail(function (error) {
                    FileUtils.showFileOpenError(fileError.code, document.file.fullPath).done(function () {
                        closeThisInline();
                    });
                });
            }
        });

        // Some tasks have to wait until we've been parented into the outer editor
        function afterAdded(inlineId) {
            myInlineId = inlineId;

            // Hide all lines other than those we want to show. We do this rather than trimming the
            // text itself so that the editor still shows accurate line numbers.
            var hidLines = false;
            if (range) {
                inlineEditorCM.operation(function () {
                    var i;
                    for (i = 0; i < range.startLine; i++) {
                        hidLines = true;
                        inlineEditorCM.hideLine(i);
                    }
                    var lineCount = inlineEditorCM.lineCount();
                    for (i = range.endLine + 1; i < lineCount; i++) {
                        hidLines = true;
                        inlineEditorCM.hideLine(i);
                    }
                });
                inlineEditor.setCursorPos(range.startLine, 0);
                _syncGutterWidths(hostEditor);
            }

            // If we haven't hidden any lines (which would have caused an update already),
            // force the editor to update its display so we measure the correct height below
            // in totalHeight().
            if (!hidLines) {
                inlineEditorCM.refresh();
            }

            // Auto-size editor to its remaining content
            var widgetHeight = inlineEditorCM.totalHeight(true);

            hostEditor._codeMirror.setInlineWidgetHeight(inlineId, widgetHeight, true);
            $(inlineEditorCM.getScrollerElement()).height(widgetHeight);
            inlineEditorCM.refresh();

            inlineEditor.focus();
        }

        return { content: inlineContent, editor: inlineEditor, height: 0, onAdded: afterAdded };
    }

    /**
     * Disposes the given document's editor if the doc is no longer "open" in the UI (visible or in
     * the working set). Otherwise does nothing.
     * @param {!Document} document
     */
    function _destroyEditorIfUnneeded(document) {
        var editor = document.editor;

        if (!editor) {
            return;
        }

        // If outgoing editor is no longer needed, dispose it
        if (!DocumentManager.getDocumentForFile(document.file)) {

            // Destroy the editor widget: CodeMirror docs for getWrapperElement() say all you have to do
            // is "Remove this from your tree to delete an editor instance."
            $(editor._codeMirror.getWrapperElement()).remove();

            // Our callers should really ensure this, but just for safety...
            if (_currentEditor === editor) {
                _currentEditorsDocument = null;
                _currentEditor = null;
            }
        }
    }

    /** Focus the currently visible editor. If no editor visible, does nothing. */
    function focusEditor() {
        if (_currentEditor) {
            _currentEditor.focus();
        }
    }

    /**
     * Resize the editor. This should only be called if the contents of the editor holder are changed
     * or if the height of the editor holder changes (except for overall window resizes, which are
     * already taken care of automatically).
     * @see #_updateEditorSize()
     */
    function resizeEditor() {
        if (_currentEditor) {
            $(_currentEditor._codeMirror.getScrollerElement()).height(_editorHolder.height());
            _currentEditor._codeMirror.refresh();
        }
    }

    /**
     * NJ's editor-resizing fix. Whenever the window resizes, we immediately adjust the editor's
     * height.
     * @see #resizeEditor()
     */
    function _updateEditorSize() {
        // The editor itself will call refresh() when it gets the window resize event.
        if (_currentEditor) {
            $(_currentEditor._codeMirror.getScrollerElement()).height(_editorHolder.height());
        }
    }

    /**
     * @private
     */
    function _doShow(document) {
        // Show new editor
        _currentEditorsDocument = document;
        _currentEditor = document.editor;

        $(_currentEditor._codeMirror.getWrapperElement()).css("display", "");

        // Window may have been resized since last time editor was visible, so kick it now
        resizeEditor();
    }

    /**
     * Make the given document's editor visible in the UI, hiding whatever was
     * visible before. Creates a new editor if none is assigned.
     * @param {!Document} document
     */
    function _showEditor(document) {
        // Hide whatever was visible before
        if (!_currentEditor) {
            $("#notEditor").css("display", "none");
        } else {
            $(_currentEditor._codeMirror.getWrapperElement()).css("display", "none");
            _destroyEditorIfUnneeded(_currentEditorsDocument);
        }

        // DocumentManager should have already ensured that we've created an Editor
        if (!document.editor) {
            throw new Error("Trying to show a currentDocument without an Editor!");
        }

        _doShow(document);
    }

    /** Hide the currently visible editor and show a placeholder UI in its place */
    function _showNoEditor() {
        if (_currentEditor) {
            $(_currentEditor._codeMirror.getWrapperElement()).css("display", "none");
            _destroyEditorIfUnneeded(_currentEditorsDocument);

            _currentEditorsDocument = null;
            _currentEditor = null;

            $("#notEditor").css("display", "");
        }
    }

    /** Handles changes to DocumentManager.getCurrentDocument() */
    function _onCurrentDocumentChange() {
        var doc = DocumentManager.getCurrentDocument();

        // Update the UI to show the right editor (or nothing), and also dispose old editor if no
        // longer needed.
        if (doc) {
            _showEditor(doc);
        } else {
            _showNoEditor();
        }
    }

    /** Handles removals from DocumentManager's working set list */
    function _onWorkingSetRemove(event, removedDoc) {
        // There's one case where an editor should be disposed even though the current document
        // didn't change: removing a document from the working set (via the "X" button). (This may
        // also cover the case where the document WAS current, if the editor-swap happens before the
        // removal from the working set.
        _destroyEditorIfUnneeded(removedDoc);
    }
    // Note: there are several paths that can lead to an editor getting destroyed
    //  - file was in working set, but not open; then closed (via working set "X" button)
    //      --> handled by _onWorkingSetRemove()
    //  - file was open, but not in working set; then navigated away from
    //      --> handled by _onCurrentDocumentChange()
    //  - file was open, but not in working set; then closed (via File > Close) (and thus implicitly
    //    navigated away from)
    //      --> handled by _onCurrentDocumentChange()
    //  - file was open AND in working set; then closed (via File > Close OR working set "X" button)
    //    (and thus implicitly navigated away from)
    //      --> handled by _onWorkingSetRemove() currently, but could be _onCurrentDocumentChange()
    //      just as easily (depends on the order of events coming from DocumentManager)

    /**
     * Designates the DOM node that will contain the currently active editor instance. EditorManager
     * will own the content of this DOM node.
     * @param {!jQueryObject} holder
     */
    function setEditorHolder(holder) {
        if (_currentEditor) {
            throw new Error("Cannot change editor area after an editor has already been created!");
        }

        _editorHolder = holder;
    }

    // Initialize: register listeners
    $(DocumentManager).on("currentDocumentChange", _onCurrentDocumentChange);
    $(DocumentManager).on("workingSetRemove", _onWorkingSetRemove);
    // Add this as a capture handler so we're guaranteed to run it before the editor does its own
    // refresh on resize.
    window.addEventListener("resize", _updateEditorSize, true);

    // Define public API
    exports.setEditorHolder = setEditorHolder;
    exports.createFullEditorForDocument = createFullEditorForDocument;
    exports.createInlineEditorFromText = createInlineEditorFromText;
    exports.focusEditor = focusEditor;
    exports.resizeEditor = resizeEditor;
    exports.registerInlineEditProvider = registerInlineEditProvider;
});
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     496
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v497 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     497
// ==/UserScript==
<|endoftext|>


<commit_before>var
	path = require('path')
	, util = require('util')
	, mkdirp = require('mkdirp')
	, upnode = require('upnode')
	, creds = require(path.resolve(__dirname, '..', '..', 'lib', 'credentials'))
	, logger = require(path.resolve(__dirname, '..', '..', 'lib', 'logger'))
	, stream = require('stream')
	, tls = require('tls')
	, net = require('net')
	, fs = require('fs')
	, existsSync = fs.existsSync || path.existsSync
;

function client(opts, app) {

	var
		modules = {}
	;

	if(!opts || Object.keys(opts).length === 0) {

		app.log.error("Invalid opts object provided");
		return false;
	}

	if(!creds || typeof creds !== 'function') {

		app.log.error("Invalid credential provider specified");
		return false;
	}

	stream.call(this);

	this.app = app;
	this.opts = opts || undefined;
	this.sendBuffer = [ ];
	this.modules = { };
	this.devices = { };
	this.log = app.log;
	creds.call(this,opts);

	this.node = undefined; // upnode
	this.transport = opts.secure ? tls : net;
	this.parameters = this.getParameters.call(this, opts);
};

util.inherits(client, stream);

client.prototype.block = require('./block');

client.prototype.getHandlers = function() {

	return {

		revokeCredentials : function revokeCredentials() {

			this.log.info('Invalid token');
			this.app.emit('client::invalidToken', true);

		}.bind(this)
		, execute : function execute(cmd, cb) {

			console.log("Command request: %s", cmd);
			this.command(cmd);

		}.bind(this)
		, update : function update(to) {

			// update client
		}.bind(this)
		, config : function config(dat, cb) {

			// configure module/device
		}
		, install : function install(mod, cb) {

			// install module
		}.bind(this)
		, uninstall : function uninstall(mod, cb) {

			// uninstall module
		}.bind(this)
	}
};

/**
 * Connect the block to the cloud
 */
client.prototype.connect = function connect() {

	var client = this;
	this.node = upnode(this.getHandlers()).connect(this.parameters);

	this.node.on('reconnect', client.reconnect.bind(client));
	this.node.on('up', client.up.bind(client));
	this.initialize();
};

/**
 * Initialize the session with the cloud after a connection
 * has been established.
 */
client.prototype.initialize = function initialize() {

	var
		flushBuffer = function flushBuffer() {

			if(!this.sendBuffer) { this.sendBuffer = [ ]; return; }
			if(this.sendBuffer.length > 0) {

				this.log.debug("Sending buffered commands...");
				this.cloud.data({

					'DEVICE' : this.sendBuffer
				});
				this.sendBuffer = [ ];
			}
			else {

				this.log.debug("No buffered commands to send");
			}
		}
		, initSession = function initSession(cloud) {

			this.cloud = cloud;

			if(this.pulse) { clearInterval(this.pulse); }
			this.pulse = setInterval(beat.bind(this), 5000);

			flushBuffer.call(this);
		}
		, beat = function beat() {

			// this.log.debug("Sending heartbeat");
			this.cloud.heartbeat(JSON.stringify({

				"TIMESTAMP" : (new Date().getTime())
				, "DEVICE" : [ ]

			}));
		}
	;

	this.app.on('client::up', initSession);
};

/**
 * cloud event handlers
 */
client.prototype.up = function up(cloud) {

	this.app.emit('client::up', cloud);
	this.log.info("Client connected to the Ninja Platform");
};

client.prototype.down = function down() {

	this.app.emit('client::down', true);
	this.log.info("Client disconnected from the Ninja Platform");
	if(this.pulse) {

		clearInterval(this.pulse);
	}
};

client.prototype.reconnect = function reconnect() {

	this.app.emit('client::reconnecting', true);
	this.log.info("Connecting to cloud...");
};

/**
 * Generate scoped parameters for dnode connection
 */
client.prototype.getParameters = function getParameters(opts) {

	var
		cloudPort = this.opts.cloudPort
		, cloudHost = this.opts.cloudHost
		, transport = this.transport
	;

	return {

		ping : 10000
		, timeout : 5000
		, reconnect : 2000
		, createStream : function createStream() {

			return transport.connect(cloudPort, cloudHost);
		}
		, block : this.block.bind(this)
	};
};

client.prototype.registerDevice = function registerDevice(device) {

	if(!device) { return; }

	device.guid = this.getGuid(device);
	device.on('data', this.dataHandler.call(this, device));
	this.log.debug("Registering device %s", device.guid);
	this.devices[device.guid] = device;
};

client.prototype.dataHandler = function dataHandler(device) {

	var self = this;
	return function(data) {

		try {

			self.sendData({

				G : device.G
				, V : device.V
				, D : device.D
				, DA : data
			});
		}
		catch(e) {

			self.log.debug("Error sending data (%s)", self.getGuid(device));
		}
	}
};

client.prototype.sendData = function sendData(dat) {

	if(!dat) { return false; }

	dat.GUID = this.getGuid(dat);
	dat.TIMESTAMP = (new Date().getTime());

	if((this.app.cloud) && this.app.cloud.data) {

		var msg = { 'DEVICE' : [ dat ] };
		return this.app.cloud.data(msg);
	}

	this.bufferData(msg);
};

client.prototype.bufferData = function bufferData(msg) {

	this.sendBuffer.push(msg);

	if(this.sendBuffer.length > 9) {

		this.sendBuffer.shift();
	}
};

client.prototype.command = function command(dat) {

	var
		self = this
		, data = this.getJSON(dat)
	;

	for(var d = 0, ds = data.DEVICE; d < ds.length; d++) {

		console.log("Executing: ");
		console.log(ds[d]);

		var
			guid = ds[d].GUID
			, device
		;
		delete ds[d].GUID;

		ds[d].G = ds[d].G.toString();

		if((device = this.devices[guid]) && typeof device.write == "function") {

			try {

				return this.devices[guid].write(ds[d].DA);
			}
			catch(e) {

				this.log.error("error actuating: %s (%s)", guid, err.message);
			}
		}
		else {

			this.log.debug("actuating %s (%s)", guid, ds[d].DA);
			// write to TTY
		}
	}
};

/**
 * Initiate module loading sequence...
 */
client.prototype.loadModule = function loadModule(name, opts, app) {

	if(!name) {

		this.log.error("loadModule error: invalid module name");
		return false;
	}

	try {

		var
			file = path.resolve(

				process.cwd()
				, 'ninja_modules'
				, name
			)
		;

		if(existsSync(file)) {

			var mod = require(file);
		}
		else {

			this.log.error("loadModule error: No such module '%s'", name);
			return null;
		}
	}
	catch(e) {

		this.log.error("loadModule error: %s", e);
		return false;
	}

	return this.addModule(name, opts, mod, app);
};

client.prototype.addModule = function addModule(name, params, mod, app) {

	if(!mod) {

		this.log.error(new Error('Invalid module provided'));
		return false;
	}

	var newModule = new mod(params, app);

	this.log.info("loadModule success: %s", name);
	if(!this.modules[name]) { this.modules[name] = {}; }

	this.modules[name][params.id] = newModule;
	this.bindModule(newModule, name);

	return this.modules[name][params.id];
};

client.prototype.bindModule = function bindModule(mod, name) {

	mod.save = function emitSave() { this.emit('save'); }.bind(mod);

	mod.on('register', this.registerDevice.bind(this));
	mod.on('error', this.moduleError.bind(mod));
	mod.on('save', this.saveHandler.call(mod, name));
	// set data handlers after registration
};

client.prototype.saveHandler = function saveHandler(name) {

	var mod = this;
	return function saveConfig() {

		var
			conf = this.opts || null
			, file = path.resolve(

				process.cwd()
				, 'config'
				, name
				, 'config.json'
			)
			, data = null
		;

		if(!conf) {

			this.log.debug("saveConfig: No config to save (%s)", name);
			return false;
		}

		data = JSON.stringify({ config : conf }, null, '\t');

		if(!data) {

			this.log.debug("saveConfig: No JSON parsed (%s)", name);
			return false;
		}

		this.log.debug("saveConfig: writing config (%s)", file);

		mkdirp(path.dirname(file), ready);

		function ready(err) {

			if(err) {

				return mod.log.error(

					"saveConfig: directory error: %s (%s)"
					, err
					, path.dirname(file)
				);
			}
			fs.writeFile(file, data, done);

		};

		function done(err) {

			if(err) {

				return mod.log.error("saveConfig: write failure (%s)", name);
			}
			mod.log.debug("saveConfig: great success! (%s)", name);
		};

		return true;

	}.bind(this);
};

client.prototype.moduleError = function moduleError(err) {

	this.log.error("Module error: %s", err);
};

client.prototype.getGuid = function getGuid(device) {

	return [

		this.serial
		, device.G
		, device.V
		, device.D

	].join('_');
};

client.prototype.getJSON = function getJSON(dat) {

	try {

		return JSON.parse(dat);
	}
	catch(e) {

		this.log.debug('Invalid JSON: %s', e);
		return false;
	}
};

function existsSync(file) {

	if(fs.existsSync) { return fs.existsSync(file); }
	return path.existsSync(file);
};

module.exports = client;
<commit_msg>Bind app logger instance to every module (as this.log).<commit_after>var
	path = require('path')
	, util = require('util')
	, mkdirp = require('mkdirp')
	, upnode = require('upnode')
	, creds = require(path.resolve(__dirname, '..', '..', 'lib', 'credentials'))
	, logger = require(path.resolve(__dirname, '..', '..', 'lib', 'logger'))
	, stream = require('stream')
	, tls = require('tls')
	, net = require('net')
	, fs = require('fs')
	, existsSync = fs.existsSync || path.existsSync
;

function client(opts, app) {

	var
		modules = {}
	;

	if(!opts || Object.keys(opts).length === 0) {

		app.log.error("Invalid opts object provided");
		return false;
	}

	if(!creds || typeof creds !== 'function') {

		app.log.error("Invalid credential provider specified");
		return false;
	}

	stream.call(this);

	this.app = app;
	this.opts = opts || undefined;
	this.sendBuffer = [ ];
	this.modules = { };
	this.devices = { };
	this.log = app.log;
	creds.call(this,opts);

	this.node = undefined; // upnode
	this.transport = opts.secure ? tls : net;
	this.parameters = this.getParameters.call(this, opts);
};

util.inherits(client, stream);

client.prototype.block = require('./block');

client.prototype.getHandlers = function() {

	return {

		revokeCredentials : function revokeCredentials() {

			this.log.info('Invalid token');
			this.app.emit('client::invalidToken', true);

		}.bind(this)
		, execute : function execute(cmd, cb) {

			console.log("Command request: %s", cmd);
			this.command(cmd);

		}.bind(this)
		, update : function update(to) {

			// update client
		}.bind(this)
		, config : function config(dat, cb) {

			// configure module/device
		}
		, install : function install(mod, cb) {

			// install module
		}.bind(this)
		, uninstall : function uninstall(mod, cb) {

			// uninstall module
		}.bind(this)
	}
};

/**
 * Connect the block to the cloud
 */
client.prototype.connect = function connect() {

	var client = this;
	this.node = upnode(this.getHandlers()).connect(this.parameters);

	this.node.on('reconnect', client.reconnect.bind(client));
	this.node.on('up', client.up.bind(client));
	this.initialize();
};

/**
 * Initialize the session with the cloud after a connection
 * has been established.
 */
client.prototype.initialize = function initialize() {

	var
		flushBuffer = function flushBuffer() {

			if(!this.sendBuffer) { this.sendBuffer = [ ]; return; }
			if(this.sendBuffer.length > 0) {

				this.log.debug("Sending buffered commands...");
				this.cloud.data({

					'DEVICE' : this.sendBuffer
				});
				this.sendBuffer = [ ];
			}
			else {

				this.log.debug("No buffered commands to send");
			}
		}
		, initSession = function initSession(cloud) {

			this.cloud = cloud;

			if(this.pulse) { clearInterval(this.pulse); }
			this.pulse = setInterval(beat.bind(this), 5000);

			flushBuffer.call(this);
		}
		, beat = function beat() {

			// this.log.debug("Sending heartbeat");
			this.cloud.heartbeat(JSON.stringify({

				"TIMESTAMP" : (new Date().getTime())
				, "DEVICE" : [ ]

			}));
		}
	;

	this.app.on('client::up', initSession);
};

/**
 * cloud event handlers
 */
client.prototype.up = function up(cloud) {

	this.app.emit('client::up', cloud);
	this.log.info("Client connected to the Ninja Platform");
};

client.prototype.down = function down() {

	this.app.emit('client::down', true);
	this.log.info("Client disconnected from the Ninja Platform");
	if(this.pulse) {

		clearInterval(this.pulse);
	}
};

client.prototype.reconnect = function reconnect() {

	this.app.emit('client::reconnecting', true);
	this.log.info("Connecting to cloud...");
};

/**
 * Generate scoped parameters for dnode connection
 */
client.prototype.getParameters = function getParameters(opts) {

	var
		cloudPort = this.opts.cloudPort
		, cloudHost = this.opts.cloudHost
		, transport = this.transport
	;

	return {

		ping : 10000
		, timeout : 5000
		, reconnect : 2000
		, createStream : function createStream() {

			return transport.connect(cloudPort, cloudHost);
		}
		, block : this.block.bind(this)
	};
};

client.prototype.registerDevice = function registerDevice(device) {

	if(!device) { return; }

	device.guid = this.getGuid(device);
	device.on('data', this.dataHandler.call(this, device));
	this.log.debug("Registering device %s", device.guid);
	this.devices[device.guid] = device;
};

client.prototype.dataHandler = function dataHandler(device) {

	var self = this;
	return function(data) {

		try {

			self.sendData({

				G : device.G
				, V : device.V
				, D : device.D
				, DA : data
			});
		}
		catch(e) {

			self.log.debug("Error sending data (%s)", self.getGuid(device));
		}
	}
};

client.prototype.sendData = function sendData(dat) {

	if(!dat) { return false; }

	dat.GUID = this.getGuid(dat);
	dat.TIMESTAMP = (new Date().getTime());

	if((this.app.cloud) && this.app.cloud.data) {

		var msg = { 'DEVICE' : [ dat ] };
		return this.app.cloud.data(msg);
	}

	this.bufferData(msg);
};

client.prototype.bufferData = function bufferData(msg) {

	this.sendBuffer.push(msg);

	if(this.sendBuffer.length > 9) {

		this.sendBuffer.shift();
	}
};

client.prototype.command = function command(dat) {

	var
		self = this
		, data = this.getJSON(dat)
	;

	for(var d = 0, ds = data.DEVICE; d < ds.length; d++) {

		console.log("Executing: ");
		console.log(ds[d]);

		var
			guid = ds[d].GUID
			, device
		;
		delete ds[d].GUID;

		ds[d].G = ds[d].G.toString();

		if((device = this.devices[guid]) && typeof device.write == "function") {

			try {

				return this.devices[guid].write(ds[d].DA);
			}
			catch(e) {

				this.log.error("error actuating: %s (%s)", guid, err.message);
			}
		}
		else {

			this.log.debug("actuating %s (%s)", guid, ds[d].DA);
			// write to TTY
		}
	}
};

/**
 * Initiate module loading sequence...
 */
client.prototype.loadModule = function loadModule(name, opts, app) {

	if(!name) {

		this.log.error("loadModule error: invalid module name");
		return false;
	}

	try {

		var
			file = path.resolve(

				process.cwd()
				, 'ninja_modules'
				, name
			)
		;

		if(existsSync(file)) {

			var mod = require(file);
		}
		else {

			this.log.error("loadModule error: No such module '%s'", name);
			return null;
		}
	}
	catch(e) {

		this.log.error("loadModule error: %s", e);
		return false;
	}

	return this.addModule(name, opts, mod, app);
};

client.prototype.addModule = function addModule(name, params, mod, app) {

	if(!mod) {

		this.log.error(new Error('Invalid module provided'));
		return false;
	}

	var newModule = new mod(params, app);

	this.log.info("loadModule success: %s", name);
	if(!this.modules[name]) { this.modules[name] = {}; }

	this.modules[name][params.id] = newModule;
	this.bindModule(newModule, name);

	return this.modules[name][params.id];
};

client.prototype.bindModule = function bindModule(mod, name) {

	mod.log = this.log;
	mod.save = function emitSave() { this.emit('save'); }.bind(mod);
	mod.on('register', this.registerDevice.bind(this));
	mod.on('error', this.moduleError.bind(mod));
	mod.on('save', this.saveHandler.call(mod, name));
	// set data handlers after registration
};

client.prototype.saveHandler = function saveHandler(name) {

	var mod = this;
	return function saveConfig() {

		var
			conf = this.opts || null
			, file = path.resolve(

				process.cwd()
				, 'config'
				, name
				, 'config.json'
			)
			, data = null
		;

		if(!conf) {

			mod.log.debug("saveConfig: No config to save (%s)", name);
			return false;
		}

		data = JSON.stringify({ config : conf }, null, '\t');

		if(!data) {

			mod.log.debug("saveConfig: No JSON parsed (%s)", name);
			return false;
		}

		this.log.debug("saveConfig: writing config (%s)", file);

		mkdirp(path.dirname(file), ready);

		function ready(err) {

			if(err) {

				return mod.log.error(

					"saveConfig: directory error: %s (%s)"
					, err
					, path.dirname(file)
				);
			}
			fs.writeFile(file, data, done);

		};

		function done(err) {

			if(err) {

				return mod.log.error("saveConfig: write failure (%s)", name);
			}
			mod.log.debug("saveConfig: great success! (%s)", name);
		};

		return true;

	}.bind(this);
};

client.prototype.moduleError = function moduleError(err) {

	this.log.error("Module error: %s", err);
};

client.prototype.getGuid = function getGuid(device) {

	return [

		this.serial
		, device.G
		, device.V
		, device.D

	].join('_');
};

client.prototype.getJSON = function getJSON(dat) {

	try {

		return JSON.parse(dat);
	}
	catch(e) {

		this.log.debug('Invalid JSON: %s', e);
		return false;
	}
};

function existsSync(file) {

	if(fs.existsSync) { return fs.existsSync(file); }
	return path.existsSync(file);
};

module.exports = client;
<|endoftext|>


<commit_before>var fs = require('fs');
var child_process = require('child_<commit_msg>Update character<commit_after>var fs = require('fs');
var child_process = require('child_p<|endoftext|>


<commit_before>// pushserv.js
// Requests schedule data of specified token
// Created on 11-10-2015
// Status 0
var request = require('request');
var PushBullet = require('pushbullet');
var mongojs = require('mongojs');
var exchangeAppcode = require('./exchangeAppcode.js');
var strtotime = require('./strtotime.js');
var sendPush = require('./sendPush.js');
var pusher = new PushBullet('nCWCoD4saWNZ8YPqlAxCkPnqcFYvgqL5');
var db = mongojs('userdata', ['users']);

function updateSendNotifications(email, id){
  db.users.update({'email':email}, {$push: {already_sent : id}}, function(){
    console.log('Added sent notification');
  });
}

function notificationIsNotSent(email, id, callback){
  db.users.find({'email':email}, function(err, doc){
    if(err){
      console.log(err);
    } else {
      if(doc[0].already_sent.indexOf(id) == -1){
        callback();
      }
    }
  });
}

function retrieveLeerlingnum(token, callback){
  var url = "https://scmoost.zportal.nl/api/v2/users/~me?access_token=" + token;
  request(url, function(err, response, body){
    if(!err & response.statusCode == 200){
      var leerlingnum = JSON.parse(response.body).response.data[0].code;
      callback(null, leerlingnum);
    } else {
      callback(err, null);
    }
  });
}

function retrieveSchedule(email, leerlingnum, token){
  var curtime = Math.round((new Date().getTime()) / 1000); // Seconds elapsed since 1-1-1970
  var startTime = curtime;
  var endTime = strtotime('next saturday', curtime);

  var roosterurl = 'http://lschoonheid.leerik.nl/beta/?id='+leerlingnum;
  var apiurl = 'https://scmoost.zportal.nl/api/v2/appointments?user=~me&start='+startTime+'&end='+endTime+'&access_token='+token+'&valid='+true;

  request(apiurl, function(err, response, body){
    if (!err && response.statusCode == 200){
      console.log('Succesful request');
      var data = JSON.parse(body).response.data;
      for(var i = 0; i < data.length; i++){
        var les = data[i].subjects;
        var leraar = data[i].teachers;
        if(data[i].cancelled === true){
          var title = les + ' van ' + leraar + ' is vervallen!';
          var body = 'Les vervallen!';
          var title = "";
          notificationIsNotSent(email, data[i].id, function(){
            sendPush(email, title, body, roosterurl);
            updateSendNotifications(email, data[i].id);
          });
        } else if(data[i].modified === true){
          var title = 'Wijziging voor ' + les + ' van ' + leraar;
          var body = data[i].changeDescription;
          var id = data[i].id;
          notificationIsNotSent(email, data[i].id, function(){
            sendPush(email, title, body, roosterurl);
            updateSendNotifications(email, id);
          });
        }
      }
    } else {
      console.log('Error occured: ' + err);
      db.close();
    }
  });
}

module.exports = function(){
  console.log('Push server started');
  setInterval(function(){
    db.users.find({first_time: 0}, function(err, docs){
      if(err){
        console.log(err);
      } else {
        for(var i = 0; i < docs.length; i++){
          var email = docs[i].email;
          var token = docs[i].token;
          retrieveLeerlingnum(docs[i].token, function(leerlingnum){
            retrieveSchedule(email, leerlingnum, token);
          });
        }
      }
    });
  }, 15 * 60 * 1000);
};
<commit_msg>Small error fix<commit_after>// pushserv.js
// Requests schedule data of specified token
// Created on 11-10-2015
// Status 0
var request = require('request');
var PushBullet = require('pushbullet');
var mongojs = require('mongojs');
var exchangeAppcode = require('./exchangeAppcode.js');
var strtotime = require('./strtotime.js');
var sendPush = require('./sendPush.js');
var pusher = new PushBullet('nCWCoD4saWNZ8YPqlAxCkPnqcFYvgqL5');
var db = mongojs('userdata', ['users']);

function updateSendNotifications(email, id){
  db.users.update({'email':email}, {$push: {already_sent : id}}, function(){
    console.log('Added sent notification');
  });
}

function notificationIsNotSent(email, id, callback){
  db.users.find({'email':email}, function(err, doc){
    if(err){
      console.log(err);
    } else {
      if(doc[0].already_sent.indexOf(id) == -1){
        callback();
      }
    }
  });
}

function retrieveLeerlingnum(token, callback){
  var url = "https://scmoost.zportal.nl/api/v2/users/~me?access_token=" + token;
  request(url, function(err, response, body){
    if(!err & response.statusCode == 200){
      var leerlingnum = JSON.parse(response.body).response.data[0].code;
      callback(null, leerlingnum);
    } else {
      callback(err, null);
    }
  });
}

function retrieveSchedule(email, leerlingnum, token){
  var curtime = Math.round((new Date().getTime()) / 1000); // Seconds elapsed since 1-1-1970
  var startTime = curtime;
  var endTime = strtotime('next saturday', curtime);

  var roosterurl = 'http://lschoonheid.leerik.nl/beta/?id='+leerlingnum;
  var apiurl = 'https://scmoost.zportal.nl/api/v2/appointments?user=~me&start='+startTime+'&end='+endTime+'&access_token='+token+'&valid='+true;

  request(apiurl, function(err, response, body){
    if (!err && response.statusCode == 200){
      console.log('Succesful request');
      var data = JSON.parse(body).response.data;
      for(var i = 0; i < data.length; i++){
        var les = data[i].subjects;
        var leraar = data[i].teachers;
        if(data[i].cancelled === true){
          var title = les + ' van ' + leraar + ' is vervallen!';
          var body = 'Les vervallen!';
          var title = "";
          var id = data[i].id;
          notificationIsNotSent(email, id, function(){
            sendPush(email, title, body, roosterurl);
            updateSendNotifications(email, id);
          });
        } else if(data[i].modified === true){
          var title = 'Wijziging voor ' + les + ' van ' + leraar;
          var body = data[i].changeDescription;
          var id = data[i].id;
          notificationIsNotSent(email, id, function(){
            sendPush(email, title, body, roosterurl);
            updateSendNotifications(email, id);
          });
        }
      }
    } else {
      console.log('Error occured: ' + err);
      db.close();
    }
  });
}

module.exports = function(){
  console.log('Push server started');
  setInterval(function(){
    db.users.find({first_time: 0}, function(err, docs){
      if(err){
        console.log(err);
      } else {
        for(var i = 0; i < docs.length; i++){
          var email = docs[i].email;
          var token = docs[i].token;
          retrieveLeerlingnum(docs[i].token, function(leerlingnum){
            retrieveSchedule(email, leerlingnum, token);
          });
        }
      }
    });
  }, 15 * 60 * 1000);
};
<|endoftext|>


<commit_before>// NOTE: QBS and Xcode's "target" and "product" names are reversed

function isBundleProduct(product)
{
    return product.type.contains("applicationbundle")
        || product.type.contains("frameworkbundle")
        || product.type.contains("bundle")
        || product.type.contains("inapppurchase");
}

// Returns the package creator code for the given product based on its type
function packageType(product)
{
    if (product.type.contains("applicationbundle"))
        return 'APPL';
    else if (product.type.contains("frameworkbundle"))
        return 'FMWK';
    else if (product.type.contains("bundle"))
        return 'BNDL';

    throw ("Unsupported product type " + product.type + ". "
         + "Must be in {applicationbundle, frameworkbundle, bundle}.");
}

function infoPlistContents(infoPlistFilePath)
{
    if (infoPlistFilePath === undefined)
        return undefined;

    var process = new Process();
    process.start("plutil", ["-convert", "json", "-o", "-", infoPlistFilePath]);
    process.waitForFinished();
    if (process.exitCode() != 0)
        throw("plutil: " + (process.readStdErr().trim() || process.readStdOut().trim()));

    return JSON.parse(process.readStdOut());
}

function infoPlistFormat(infoPlistFilePath)
{
    if (infoPlistFilePath === undefined)
        return undefined;

    // Verify that the Info.plist format is actually valid in the first place
    var process = new Process();
    process.start("plutil", ["-lint", infoPlistFilePath]);
    process.waitForFinished();
    if (process.exitCode() != 0)
        throw("plutil: " + (process.readStdErr().trim() || process.readStdOut().trim()));

    process = new Process();
    process.start("file", [infoPlistFilePath]);
    process.waitForFinished();
    var magic = process.readStdOut().trim();

    if (magic.indexOf(infoPlistFilePath + ": ") !== 0)
        throw("Unexpected output from file command: " + magic);

    magic = magic.slice(infoPlistFilePath.length + 2);

    if (magic === "Apple binary property list")
        return "binary1";
    else if (magic.indexOf("XML") === 0)
        return "xml1";
    else if (magic.indexOf("UTF-8 Unicode text") === 0)
        return "json";

    return undefined;
}

// CONTENTS_FOLDER_PATH
// Main bundle directory
// the version parameter is only used for framework bundles
function contentsFolderPath(product, version)
{
    var path = wrapperName(product);

    if (product.type.contains("frameworkbundle"))
        path += "/Versions/" + (version || frameworkVersion(product));
    else if (!isShallowBundle(product))
        path += "/Contents";

    return path;
}

// DOCUMENTATION_FOLDER_PATH
// Directory for documentation files
// the version parameter is only used for framework bundles
function documentationFolderPath(product, localizationName, version)
{
    var path = localizedResourcesFolderPath(product, localizationName, version);
    if (!product.type.contains("inapppurchase"))
        path += "/Documentation";
    return path;
}

// EXECUTABLES_FOLDER_PATH
// Destination directory for auxiliary executables
// the version parameter is only used for framework bundles
function executablesFolderPath(product, localizationName, version)
{
    if (product.type.contains("frameworkbundle"))
        return localizedResourcesFolderPath(product, localizationName, version);
    else
        return _contentsFolderSubDirPath(product, "Executables", version);
}

// EXECUTABLE_FOLDER_PATH
// Destination directory for the primary executable
// the version parameter is only used for framework bundles
function executableFolderPath(product, version)
{
    var path = contentsFolderPath(product, version);
    if (!isShallowBundle(product)
        && !product.type.contains("frameworkbundle")
        && !product.type.contains("inapppurchase"))
        path += "/MacOS";

    return path;
}

// EXECUTABLE_PATH
// Path to the bundle's primary executable file
// the version parameter is only used for framework bundles
function executablePath(product, version)
{
    return executableFolderPath(product, version) + "/" + productName(product);
}

// FRAMEWORK_VERSION
// Major version number or letter corresponding to the bundle version
function frameworkVersion(product)
{
    if (!product.type.contains("frameworkbundle"))
        throw "Product type must be a frameworkbundle, was " + product.type;

    var n = parseInt(product.version, 10);
    return isNaN(n) ? 'A' : n;
}

// FRAMEWORKS_FOLDER_PATH
// Directory containing frameworks used by the bundle's executables
// the version parameter is only used for framework bundles
function frameworksFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "Frameworks", version);
}

// INFOPLIST_PATH
// Path to the bundle's main information property list
// the version parameter is only used for framework bundles
function infoPlistPath(product, version)
{
    var path;
    if (product.type.contains("frameworkbundle"))
        path = unlocalizedResourcesFolderPath(product, version);
    else if (product.type.contains("inapppurchase"))
        path = wrapperName(product);
    else
        path = contentsFolderPath(product, version);

    return path + "/" + _infoFileNames(product)[0];
}

// INFOSTRINGS_PATH
// Path to the strings file corresponding to the bundle's main information property list
// the version parameter is only used for framework bundles
function infoStringsPath(product, localizationName, version)
{
    return localizedResourcesFolderPath(product, localizationName, version) + "/" + _infoFileNames(product)[1];
}

// LOCALIZED_RESOURCES_FOLDER_PATH
// Path to the bundle's resources directory for the given localization
// the version parameter is only used for framework bundles
function localizedResourcesFolderPath(product, localizationName, version)
{
    if (typeof localizationName !== "string")
        throw("'" + localizationName + "' is not a valid localization name");

    return unlocalizedResourcesFolderPath(product, version) + "/" + localizationName + ".lproj";
}

// PKGINFO_PATH
// Path to the bundle's PkgInfo file
function pkgInfoPath(product)
{
    var path = (product.type.contains("frameworkbundle"))
        ? wrapperName(product)
        : contentsFolderPath(product);
    return path + "/PkgInfo";
}

// PLUGINS_FOLDER_PATH
// Directory containing plugins used by the bundle's executables
// the version parameter is only used for framework bundles
function pluginsFolderPath(product, version)
{
    if (product.type.contains("frameworkbundle"))
        return unlocalizedResourcesFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "PlugIns", version);
}

// PRIVATE_HEADERS_FOLDER_PATH
// Directory containing private header files for the framework
// the version parameter is only used for framework bundles
function privateHeadersFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "PrivateHeaders", version);
}

// PRODUCT_NAME
// The name of the product (in Xcode terms) which corresponds to the target name in QBS terms
function productName(product)
{
    return product.targetName;
}

// PUBLIC_HEADERS_FOLDER_PATH
// Directory containing public header files for the framework
// the version parameter is only used for framework bundles
function publicHeadersFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "Headers", version);
}

// SCRIPTS_FOLDER_PATH
// Directory containing script files associated with the bundle
// the version parameter is only used for framework bundles
function scriptsFolderPath(product, version)
{
    return unlocalizedResourcesFolderPath(product, version) + "/Scripts";
}

// SHALLOW_BUNDLE
// Controls the presence or absence of the Contents, MacOS and Resources folders
// iOS tends to store the majority of files in its bundles in the main directory
function isShallowBundle(product)
{
    return product.moduleProperty("qbs", "targetOS").contains("ios")
        && product.type.contains("applicationbundle");
}

// SHARED_FRAMEWORKS_FOLDER_PATH
// Directory containing sub-frameworks that may be shared with other applications
// the version parameter is only used for framework bundles
function sharedFrameworksFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "SharedFrameworks", version);
}

// SHARED_SUPPORT_FOLDER_PATH
// Directory containing supporting files that may be shared with other applications
// the version parameter is only used for framework bundles
function sharedSupportFolderPath(product, version)
{
    if (product.type.contains("frameworkbundle"))
        return unlocalizedResourcesFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "SharedSupport", version);
}

// UNLOCALIZED_RESOURCES_FOLDER_PATH
// Directory containing resource files that are not specific to any given localization
function unlocalizedResourcesFolderPath(product, version)
{
    if (isShallowBundle(product))
        return contentsFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "Resources", version);
}

// VERSIONPLIST_PATH
// Directory containing the bundle's version.plist file
// the version parameter is only used for framework bundles
function versionPlistPath(product, version)
{
    var path = (product.type.contains("frameworkbundle"))
        ? unlocalizedResourcesFolderPath(product, version)
        : contentsFolderPath(product, version);
    return path + "/version.plist";
}

// WRAPPER_EXTENSION
// The file extension of the bundle directory - app, framework, bundle, etc.
function wrapperExtension(product)
{
    if (product.type.contains("applicationbundle")) {
        return "app";
    } else if (product.type.contains("frameworkbundle")) {
        return "framework";
    } else if (product.type.contains("inapppurchase")) {
        return "";
    } else if (product.type.contains("bundle")) {
        // Potentially: kext, prefPane, qlgenerator, saver, mdimporter, or a custom extension
        var bundleExtension = ModUtils.moduleProperty(product, "bundleExtension");

        // default to bundle if none was specified by the user
        return bundleExtension || "bundle";
    } else {
        throw ("Unsupported bundle product type " + product.type + ". "
             + "Must be in {applicationbundle, frameworkbundle, bundle, inapppurchase}.");
    }
}

// WRAPPER_NAME
// The name of the bundle directory - the product name plus the bundle extension
function wrapperName(product)
{
    return productName(product) + wrapperSuffix(product);
}

// WRAPPER_SUFFIX
// The suffix of the bundle directory, that is, its extension prefixed with a '.',
// or an empty string if the extension is also an empty string
function wrapperSuffix(product)
{
    var ext = wrapperExtension(product);
    return ext ? ("." + ext) : "";
}

// Private helper functions

// In-App purchase content bundles use virtually no subfolders of Contents;
// this is a convenience method to avoid repeating that logic everywhere
// the version parameter is only used for framework bundles
function _contentsFolderSubDirPath(product, subdirectoryName, version)
{
    var path = contentsFolderPath(product, version);
    if (!product.type.contains("inapppurchase"))
        path += "/" + subdirectoryName;
    return path;
}

// Returns a list containing the filename of the bundle's main information
// property list and filename of the corresponding strings file
function _infoFileNames(product)
{
    if (product.type.contains("inapppurchase"))
        return ["ContentInfo.plist", "ContentInfo.strings"];
    else
        return ["Info.plist", "InfoPlist.strings"];
}
<commit_msg>Fix up file type detection code.<commit_after>// NOTE: QBS and Xcode's "target" and "product" names are reversed

function isBundleProduct(product)
{
    return product.type.contains("applicationbundle")
        || product.type.contains("frameworkbundle")
        || product.type.contains("bundle")
        || product.type.contains("inapppurchase");
}

// Returns the package creator code for the given product based on its type
function packageType(product)
{
    if (product.type.contains("applicationbundle"))
        return 'APPL';
    else if (product.type.contains("frameworkbundle"))
        return 'FMWK';
    else if (product.type.contains("bundle"))
        return 'BNDL';

    throw ("Unsupported product type " + product.type + ". "
         + "Must be in {applicationbundle, frameworkbundle, bundle}.");
}

function infoPlistContents(infoPlistFilePath)
{
    if (infoPlistFilePath === undefined)
        return undefined;

    var process = new Process();
    process.start("plutil", ["-convert", "json", "-o", "-", infoPlistFilePath]);
    process.waitForFinished();
    if (process.exitCode() != 0)
        throw("plutil: " + (process.readStdErr().trim() || process.readStdOut().trim()));

    return JSON.parse(process.readStdOut());
}

function infoPlistFormat(infoPlistFilePath)
{
    if (infoPlistFilePath === undefined)
        return undefined;

    // Verify that the Info.plist format is actually valid in the first place
    var process = new Process();
    process.start("plutil", ["-lint", infoPlistFilePath]);
    process.waitForFinished();
    if (process.exitCode() != 0)
        throw("plutil: " + (process.readStdErr().trim() || process.readStdOut().trim()));

    process = new Process();
    process.start("file", ["-bI", infoPlistFilePath]);
    process.waitForFinished();
    var magic = process.readStdOut().trim();

    if (magic.indexOf("application/octet-stream;") === 0)
        return "binary1";
    else if (magic.indexOf("application/xml;") === 0)
        return "xml1";
    else if (magic.indexOf("text/plain;") == 0)
        return "json";

    return undefined;
}

// CONTENTS_FOLDER_PATH
// Main bundle directory
// the version parameter is only used for framework bundles
function contentsFolderPath(product, version)
{
    var path = wrapperName(product);

    if (product.type.contains("frameworkbundle"))
        path += "/Versions/" + (version || frameworkVersion(product));
    else if (!isShallowBundle(product))
        path += "/Contents";

    return path;
}

// DOCUMENTATION_FOLDER_PATH
// Directory for documentation files
// the version parameter is only used for framework bundles
function documentationFolderPath(product, localizationName, version)
{
    var path = localizedResourcesFolderPath(product, localizationName, version);
    if (!product.type.contains("inapppurchase"))
        path += "/Documentation";
    return path;
}

// EXECUTABLES_FOLDER_PATH
// Destination directory for auxiliary executables
// the version parameter is only used for framework bundles
function executablesFolderPath(product, localizationName, version)
{
    if (product.type.contains("frameworkbundle"))
        return localizedResourcesFolderPath(product, localizationName, version);
    else
        return _contentsFolderSubDirPath(product, "Executables", version);
}

// EXECUTABLE_FOLDER_PATH
// Destination directory for the primary executable
// the version parameter is only used for framework bundles
function executableFolderPath(product, version)
{
    var path = contentsFolderPath(product, version);
    if (!isShallowBundle(product)
        && !product.type.contains("frameworkbundle")
        && !product.type.contains("inapppurchase"))
        path += "/MacOS";

    return path;
}

// EXECUTABLE_PATH
// Path to the bundle's primary executable file
// the version parameter is only used for framework bundles
function executablePath(product, version)
{
    return executableFolderPath(product, version) + "/" + productName(product);
}

// FRAMEWORK_VERSION
// Major version number or letter corresponding to the bundle version
function frameworkVersion(product)
{
    if (!product.type.contains("frameworkbundle"))
        throw "Product type must be a frameworkbundle, was " + product.type;

    var n = parseInt(product.version, 10);
    return isNaN(n) ? 'A' : n;
}

// FRAMEWORKS_FOLDER_PATH
// Directory containing frameworks used by the bundle's executables
// the version parameter is only used for framework bundles
function frameworksFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "Frameworks", version);
}

// INFOPLIST_PATH
// Path to the bundle's main information property list
// the version parameter is only used for framework bundles
function infoPlistPath(product, version)
{
    var path;
    if (product.type.contains("frameworkbundle"))
        path = unlocalizedResourcesFolderPath(product, version);
    else if (product.type.contains("inapppurchase"))
        path = wrapperName(product);
    else
        path = contentsFolderPath(product, version);

    return path + "/" + _infoFileNames(product)[0];
}

// INFOSTRINGS_PATH
// Path to the strings file corresponding to the bundle's main information property list
// the version parameter is only used for framework bundles
function infoStringsPath(product, localizationName, version)
{
    return localizedResourcesFolderPath(product, localizationName, version) + "/" + _infoFileNames(product)[1];
}

// LOCALIZED_RESOURCES_FOLDER_PATH
// Path to the bundle's resources directory for the given localization
// the version parameter is only used for framework bundles
function localizedResourcesFolderPath(product, localizationName, version)
{
    if (typeof localizationName !== "string")
        throw("'" + localizationName + "' is not a valid localization name");

    return unlocalizedResourcesFolderPath(product, version) + "/" + localizationName + ".lproj";
}

// PKGINFO_PATH
// Path to the bundle's PkgInfo file
function pkgInfoPath(product)
{
    var path = (product.type.contains("frameworkbundle"))
        ? wrapperName(product)
        : contentsFolderPath(product);
    return path + "/PkgInfo";
}

// PLUGINS_FOLDER_PATH
// Directory containing plugins used by the bundle's executables
// the version parameter is only used for framework bundles
function pluginsFolderPath(product, version)
{
    if (product.type.contains("frameworkbundle"))
        return unlocalizedResourcesFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "PlugIns", version);
}

// PRIVATE_HEADERS_FOLDER_PATH
// Directory containing private header files for the framework
// the version parameter is only used for framework bundles
function privateHeadersFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "PrivateHeaders", version);
}

// PRODUCT_NAME
// The name of the product (in Xcode terms) which corresponds to the target name in QBS terms
function productName(product)
{
    return product.targetName;
}

// PUBLIC_HEADERS_FOLDER_PATH
// Directory containing public header files for the framework
// the version parameter is only used for framework bundles
function publicHeadersFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "Headers", version);
}

// SCRIPTS_FOLDER_PATH
// Directory containing script files associated with the bundle
// the version parameter is only used for framework bundles
function scriptsFolderPath(product, version)
{
    return unlocalizedResourcesFolderPath(product, version) + "/Scripts";
}

// SHALLOW_BUNDLE
// Controls the presence or absence of the Contents, MacOS and Resources folders
// iOS tends to store the majority of files in its bundles in the main directory
function isShallowBundle(product)
{
    return product.moduleProperty("qbs", "targetOS").contains("ios")
        && product.type.contains("applicationbundle");
}

// SHARED_FRAMEWORKS_FOLDER_PATH
// Directory containing sub-frameworks that may be shared with other applications
// the version parameter is only used for framework bundles
function sharedFrameworksFolderPath(product, version)
{
    return _contentsFolderSubDirPath(product, "SharedFrameworks", version);
}

// SHARED_SUPPORT_FOLDER_PATH
// Directory containing supporting files that may be shared with other applications
// the version parameter is only used for framework bundles
function sharedSupportFolderPath(product, version)
{
    if (product.type.contains("frameworkbundle"))
        return unlocalizedResourcesFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "SharedSupport", version);
}

// UNLOCALIZED_RESOURCES_FOLDER_PATH
// Directory containing resource files that are not specific to any given localization
function unlocalizedResourcesFolderPath(product, version)
{
    if (isShallowBundle(product))
        return contentsFolderPath(product, version);

    return _contentsFolderSubDirPath(product, "Resources", version);
}

// VERSIONPLIST_PATH
// Directory containing the bundle's version.plist file
// the version parameter is only used for framework bundles
function versionPlistPath(product, version)
{
    var path = (product.type.contains("frameworkbundle"))
        ? unlocalizedResourcesFolderPath(product, version)
        : contentsFolderPath(product, version);
    return path + "/version.plist";
}

// WRAPPER_EXTENSION
// The file extension of the bundle directory - app, framework, bundle, etc.
function wrapperExtension(product)
{
    if (product.type.contains("applicationbundle")) {
        return "app";
    } else if (product.type.contains("frameworkbundle")) {
        return "framework";
    } else if (product.type.contains("inapppurchase")) {
        return "";
    } else if (product.type.contains("bundle")) {
        // Potentially: kext, prefPane, qlgenerator, saver, mdimporter, or a custom extension
        var bundleExtension = ModUtils.moduleProperty(product, "bundleExtension");

        // default to bundle if none was specified by the user
        return bundleExtension || "bundle";
    } else {
        throw ("Unsupported bundle product type " + product.type + ". "
             + "Must be in {applicationbundle, frameworkbundle, bundle, inapppurchase}.");
    }
}

// WRAPPER_NAME
// The name of the bundle directory - the product name plus the bundle extension
function wrapperName(product)
{
    return productName(product) + wrapperSuffix(product);
}

// WRAPPER_SUFFIX
// The suffix of the bundle directory, that is, its extension prefixed with a '.',
// or an empty string if the extension is also an empty string
function wrapperSuffix(product)
{
    var ext = wrapperExtension(product);
    return ext ? ("." + ext) : "";
}

// Private helper functions

// In-App purchase content bundles use virtually no subfolders of Contents;
// this is a convenience method to avoid repeating that logic everywhere
// the version parameter is only used for framework bundles
function _contentsFolderSubDirPath(product, subdirectoryName, version)
{
    var path = contentsFolderPath(product, version);
    if (!product.type.contains("inapppurchase"))
        path += "/" + subdirectoryName;
    return path;
}

// Returns a list containing the filename of the bundle's main information
// property list and filename of the corresponding strings file
function _infoFileNames(product)
{
    if (product.type.contains("inapppurchase"))
        return ["ContentInfo.plist", "ContentInfo.strings"];
    else
        return ["Info.plist", "InfoPlist.strings"];
}
<|endoftext|>


<commit_before>import { _ } from 'meteor/underscore';
import { Tracker } from 'meteor/tracker';
import { Match } from 'meteor/check';
import { BasicCollection } from '../utils/BasicCollection';
import { BasicModel } from '../utils/BasicModel';
import { allowBuffKeyList, servantCollection } from './servantModel';

export class UseServantModel extends BasicModel {
  get collection() {
    return useServantCollection;
  }
  get schema() {
    return {
      id: String,
      servantId: String,
      hpPercentage: Number,
      currentNp: Match.Integer,
      temporaryBuff: [
        {
          name: new Match.OneOf(...allowBuffKeyList),
          number: Number,
          limitTarget: new Match.Optional(String)
        }
      ]
    };
  }
  get defaults() {
    return {
      id: '',
      servantId: '',
      hpPercentage: 100,
      currentNp: 0,
      temporaryBuff: []
    };
  }
  //associate with servant data
  get servantData() {
    return servantCollection.get(this.servantId) || null;
  }
  get atk() {
    const servantData = this.servantData;

    return servantData ? servantData.atk : null;
  }
  get classType() {
    const servantData = this.servantData;

    return servantData ? servantData.classType : null;
  }
  get alignmentType() {
    const servantData = this.servantData;

    return servantData ? servantData.alignmentType : null;
  }
  get starDrop() {
    const servantData = this.servantData;

    return servantData ? servantData.starDrop : null;
  }
  get cards() {
    const servantData = this.servantData;

    return servantData ? servantData.cards : null;
  }
  get hits() {
    const servantData = this.servantData;

    return servantData ? servantData.hits : null;
  }
  get npGain() {
    const servantData = this.servantData;

    return servantData ? servantData.npGain : null;
  }
  get weaponList() {
    const servantData = this.servantData;

    return servantData ? servantData.weaponList : null;
  }
  get useWeaponIndex() {
    const servantData = this.servantData;

    return servantData ? servantData.useWeaponIndex : null;
  }
  get weapon() {
    const servantData = this.servantData;

    return servantData ? servantData.weapon : null;
  }
  get weaponLevel() {
    const servantData = this.servantData;

    return servantData ? servantData.weaponLevel : null;
  }
  //allowed set servantData's field, but won't really change data
  set nickname(value) {
    return this;
  }
  set atk(value) {
    return this;
  }
  set useWeaponIndex(value) {
    return this;
  }
  set weaponLevel(value) {
    return this;
  }
  set lastUseTime(value) {
    return this;
  }
  //compute data
  get name() {
    const servantData = this.servantData;

    if (servantData) {
      return servantData.nickname || servantData.fullname;
    }
    else {
      return '空缺';
    }
  }
  get fullname() {
    const servantData = this.servantData;

    return servantData ? servantData.fullname : '空缺';
  }
  get maximumNp() {
    const servantData = this.servantData;

    return servantData ? (servantData.weaponLevel * 100) : null;
  }
  get buffHash() {
    const buffHash = {};
    _.each(allowBuffKeyList, (buffKey) => {
      if (buffKey === 'specialBoost') {
        buffHash.specialBoost = [];
      }
      else {
        buffHash[buffKey] = 0;
      }
    });
    _.each(this.servantData.passiveBuff, (buff) => {
      const buffKey = buff.name;
      if (buffKey === 'specialBoost') {
        buffHash.specialBoost.push({
          limitTarget: buff.limitTarget,
          number: buff.number
        });
      }
      else {
        buffHash[buffKey] += buff.number;
      }
    });
    _.each(this.temporaryBuff, (buff) => {
      const buffKey = buff.name;
      if (buffKey === 'specialBoost') {
        buffHash.specialBoost.push({
          limitTarget: buff.limitTarget,
          number: buff.number
        });
      }
      else {
        buffHash[buffKey] += buff.number;
      }
    });

    return buffHash;
  }
}

//create and load use servant data
const storageUseServantList = localStorage.getItem('useServantList');
const useServantObjectList = storageUseServantList ? JSON.parse(storageUseServantList) : [
  {
    id: '1'
  },
  {
    id: '2'
  },
  {
    id: '3'
  }
];
const useServantDataList = _.map(useServantObjectList, (useServantObject) => {
  return new UseServantModel(useServantObject);
});
export const useServantCollection = new BasicCollection(useServantDataList);
//auto save use servant data
Tracker.autorun(function() {
  const storageUseServantList = JSON.stringify(useServantCollection);
  localStorage.setItem('useServantList', storageUseServantList);
});
<commit_msg>修正英靈np上限計算錯誤的bug。<commit_after>import { _ } from 'meteor/underscore';
import { Tracker } from 'meteor/tracker';
import { Match } from 'meteor/check';
import { BasicCollection } from '../utils/BasicCollection';
import { BasicModel } from '../utils/BasicModel';
import { allowBuffKeyList, servantCollection } from './servantModel';

export class UseServantModel extends BasicModel {
  get collection() {
    return useServantCollection;
  }
  get schema() {
    return {
      id: String,
      servantId: String,
      hpPercentage: Number,
      currentNp: Match.Integer,
      temporaryBuff: [
        {
          name: new Match.OneOf(...allowBuffKeyList),
          number: Number,
          limitTarget: new Match.Optional(String)
        }
      ]
    };
  }
  get defaults() {
    return {
      id: '',
      servantId: '',
      hpPercentage: 100,
      currentNp: 0,
      temporaryBuff: []
    };
  }
  //associate with servant data
  get servantData() {
    return servantCollection.get(this.servantId) || null;
  }
  get atk() {
    const servantData = this.servantData;

    return servantData ? servantData.atk : null;
  }
  get classType() {
    const servantData = this.servantData;

    return servantData ? servantData.classType : null;
  }
  get alignmentType() {
    const servantData = this.servantData;

    return servantData ? servantData.alignmentType : null;
  }
  get starDrop() {
    const servantData = this.servantData;

    return servantData ? servantData.starDrop : null;
  }
  get cards() {
    const servantData = this.servantData;

    return servantData ? servantData.cards : null;
  }
  get hits() {
    const servantData = this.servantData;

    return servantData ? servantData.hits : null;
  }
  get npGain() {
    const servantData = this.servantData;

    return servantData ? servantData.npGain : null;
  }
  get weaponList() {
    const servantData = this.servantData;

    return servantData ? servantData.weaponList : null;
  }
  get useWeaponIndex() {
    const servantData = this.servantData;

    return servantData ? servantData.useWeaponIndex : null;
  }
  get weapon() {
    const servantData = this.servantData;

    return servantData ? servantData.weapon : null;
  }
  get weaponLevel() {
    const servantData = this.servantData;

    return servantData ? servantData.weaponLevel : null;
  }
  //allowed set servantData's field, but won't really change data
  set nickname(value) {
    return this;
  }
  set atk(value) {
    return this;
  }
  set useWeaponIndex(value) {
    return this;
  }
  set weaponLevel(value) {
    return this;
  }
  set lastUseTime(value) {
    return this;
  }
  //compute data
  get name() {
    const servantData = this.servantData;

    if (servantData) {
      return servantData.nickname || servantData.fullname;
    }
    else {
      return '空缺';
    }
  }
  get fullname() {
    const servantData = this.servantData;

    return servantData ? servantData.fullname : '空缺';
  }
  get maximumNp() {
    const servantData = this.servantData;

    if (servantData) {
      const weaponLevel = servantData.weaponLevel;
      if (weaponLevel >= 5) {
        return 300;
      }
      else if (weaponLevel >= 2) {
        return 200;
      }
      else {
        return 100;
      }
    }
    else {
      return null;
    }
    return servantData ? Math.min(servantData.weaponLevel * 100, 300) : null;
  }
  get buffHash() {
    const buffHash = {};
    _.each(allowBuffKeyList, (buffKey) => {
      if (buffKey === 'specialBoost') {
        buffHash.specialBoost = [];
      }
      else {
        buffHash[buffKey] = 0;
      }
    });
    _.each(this.servantData.passiveBuff, (buff) => {
      const buffKey = buff.name;
      if (buffKey === 'specialBoost') {
        buffHash.specialBoost.push({
          limitTarget: buff.limitTarget,
          number: buff.number
        });
      }
      else {
        buffHash[buffKey] += buff.number;
      }
    });
    _.each(this.temporaryBuff, (buff) => {
      const buffKey = buff.name;
      if (buffKey === 'specialBoost') {
        buffHash.specialBoost.push({
          limitTarget: buff.limitTarget,
          number: buff.number
        });
      }
      else {
        buffHash[buffKey] += buff.number;
      }
    });

    return buffHash;
  }
}

//create and load use servant data
const storageUseServantList = localStorage.getItem('useServantList');
const useServantObjectList = storageUseServantList ? JSON.parse(storageUseServantList) : [
  {
    id: '1'
  },
  {
    id: '2'
  },
  {
    id: '3'
  }
];
const useServantDataList = _.map(useServantObjectList, (useServantObject) => {
  return new UseServantModel(useServantObject);
});
export const useServantCollection = new BasicCollection(useServantDataList);
//auto save use servant data
Tracker.autorun(function() {
  const storageUseServantList = JSON.stringify(useServantCollection);
  localStorage.setItem('useServantList', storageUseServantList);
});
<|endoftext|>


<commit_before>/* @flow */
import React, { PureComponent } from 'react';
import { StyleSheet, View } from 'react-native';

import type { Narrow } from '../types';
import connectWithActions from '../connectWithActions';
import { ViewPlaceholder } from '../common';
import { getTitleTextColor } from '../selectors';
import { getInfoButtonFromNarrow, getExtraButtonFromNarrow } from './titleButtonFromNarrow';

const styles = StyleSheet.create({
  wrapper: {
    flexDirection: 'row',
  },
});

type Props = {
  color: string,
  narrow: Narrow,
};

class TitleNavButtons extends PureComponent<Props> {
  props: Props;

  render() {
    const { color, narrow } = this.props;
    const InfoButton = getInfoButtonFromNarrow(narrow);
    const ExtraButton = getExtraButtonFromNarrow(narrow);

    return (
      <View style={styles.wrapper}>
        {ExtraButton ? (
          <ExtraButton color={color} narrow={narrow} />
        ) : (
          <ViewPlaceholder width={44} />
        )}
        {InfoButton ? <InfoButton color={color} narrow={narrow} /> : <ViewPlaceholder width={44} />}
      </View>
    );
  }
}

export default connectWithActions((state, props) => ({
  color: getTitleTextColor(props.narrow)(state),
}))(TitleNavButtons);
<commit_msg>title nav buttons: Use standard Redux dispatch.<commit_after>/* @flow */
import { connect } from 'react-redux';

import React, { PureComponent } from 'react';
import { StyleSheet, View } from 'react-native';

import type { Narrow } from '../types';
import { ViewPlaceholder } from '../common';
import { getTitleTextColor } from '../selectors';
import { getInfoButtonFromNarrow, getExtraButtonFromNarrow } from './titleButtonFromNarrow';

const styles = StyleSheet.create({
  wrapper: {
    flexDirection: 'row',
  },
});

type Props = {
  color: string,
  narrow: Narrow,
};

class TitleNavButtons extends PureComponent<Props> {
  props: Props;

  render() {
    const { color, narrow } = this.props;
    const InfoButton = getInfoButtonFromNarrow(narrow);
    const ExtraButton = getExtraButtonFromNarrow(narrow);

    return (
      <View style={styles.wrapper}>
        {ExtraButton ? (
          <ExtraButton color={color} narrow={narrow} />
        ) : (
          <ViewPlaceholder width={44} />
        )}
        {InfoButton ? <InfoButton color={color} narrow={narrow} /> : <ViewPlaceholder width={44} />}
      </View>
    );
  }
}

export default connect((state, props) => ({
  color: getTitleTextColor(props.narrow)(state),
}))(TitleNavButtons);
<|endoftext|>