<commit_before>/*#ifndef(UMD)*/
"use strict";
/*global _GPF_EVENT_DATA*/ // gpf.events.EVENT_DATA
/*global _GPF_EVENT_END_OF_DATA*/ // gpf.events.EVENT_END_OF_DATA
/*global _GPF_EVENT_READY*/ // gpf.events.EVENT_READY
/*global _GPF_FS_TYPE_DIRECTORY*/ // _GPF_FS_TYPE_DIRECTORY
/*global _GPF_FS_TYPE_FILE*/ // _GPF_FS_TYPE_FILE
/*global _GPF_FS_TYPE_NOT_FOUND*/ // _GPF_FS_TYPE_NOT_FOUND
/*global _gpfArrayEnumerator*/ // Create an IEnumerator from an array
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfEventsFire*/ // gpf.events.fire (internal, parameters must match)
/*global _gpfHost*/ // Host type
/*global _gpfI*/ // gpf.interfaces
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*global _gpfMsFSO:true*/ // Scripting.FileSystemObject activeX
/*global _gpfPathDecompose*/ // Normalize path and returns an array of parts
/*global _gpfPathNormalize*/ // Normalize path
// /*#endif*/

var
    /**
     * @type {gpf.fs.WScriptFileStorage}
     * @private
     */
    _gpfWScriptFileStorage,

    /**
     * Translate WScript object info into a file info
     *
     * @param {Scripting.FileSystemObject.File
     * |Scripting.FileSystemObject.Folder} fsoObj
     * @param {Number} type
     * @private
     */
    _gpfFsoObjToInfo = function (fsoObj, type) {
        return {
            type: type,
            fileName: fsoObj.Name.toLowerCase(),
            filePath: _gpfPathNormalize(fsoObj.Path),
            size: fsoObj.Size,
            createdDateTime: fsoObj.DateCreated,
            modifiedDateTime: fsoObj.DateLastModified
        };
    },

    /**
     * String representing character & binary mapping
     *
     * @type {String|String[]}
     * @private
     */
    _gpf437chars = "C7|FC|E9|E2|E4|E0|E5|E7|EA|EB|E8|EF|EE|EC|C4|C5|C9|E6|C6" +
        "|F4|F6|F2|FB|F9|FF|D6|DC|A2|A3|A5|20A7|192|E1|ED|F3|FA|F1|D1|AA|BA" +
        "|BF|2310|AC|BD|BC|A1|AB|BB|2591|2592|2593|2502|2524|2561|2562|2556" +
        "|2555|2563|2551|2557|255D|255C|255B|2510|2514|2534|252C|251C|2500" +
        "|253C|255E|255F|255A|2554|2569|2566|2560|2550|256C|2567|2568|2564" +
        "|2565|2559|2558|2552|2553|256B|256A|2518|250C|2588|2584|258C|2590" +
        "|2580|3B1|DF|393|3C0|3A3|3C3|B5|3C4|3A6|398|3A9|3B4|221E|3C6|3B5" +
        "|2229|2261|B1|2265|2264|2320|2321|F7|2248|B0|2219|B7|221A|207F" +
        "|B2|25A0|A0",

    /**
     * Convert hexadecimal character code into a character
     *
     * @param {String} hexa Hexadecimal character code
     * @returns {String} Character
     * @private
     */
    _gpfCharCodeFromHexa = function (hexa) {
        return String.fromCharCode(parseInt(hexa, 16));
    },

    /**
     * Build the 256 characters composing the code page 437
     *
     * @private
     */
    _gpfBuild437chars = function () {
        var asciiChars = [],
            idx;
        for (idx = 128; idx > 0;) {
            asciiChars.unshift(String.fromCharCode(--idx));
        }
        _gpf437chars = asciiChars.concat(_gpf437chars
            .split("|")
            .map(_gpfCharCodeFromHexa)
        );
    },

    /**
     * Binary stream base class
     *
     * @class {WScriptBinaryStream}
     * @private
     */
    _gpfWScriptBinStream = _gpfDefine("WScriptBinaryStream", {

        public: {

            /**
             * @param {String} path
             * @param {Boolean} [load=false] load load from file
             * @constructor
             */
            constructor: function (path, load) {
                this._path = _gpfPathDecompose(path).join("\\");
                var stream = this._adoStream
                    = new ActiveXObject("ADODB.Stream");
                // https://en.wikipedia.org/wiki/Code_page_437
                stream.Open();
                if (load) {
                    stream.LoadFromFile(this._path);
                }
                stream.Position = 0;
                stream.Type = 2; /*adTypeText*/
                stream.CharSet = "437";
                if (256 !== _gpf437chars.length) {
                    _gpfBuild437chars();
                }
            },

            /**
             * Close the stream
             */
            close: function () {
                this._adoStream.Close();
            }

        },

        protected: {

            /**
             * File path
             * @type {String}
             */
            _path: "",

            /**
             * ADODB.Stream
             * @type {Object}
             */
            _adoStream: null

        }

    }),

    /**
     * Binary stream reader
     *
     * @class {WScriptBinaryReadStream}
     * @implements {gpf.interfaces.IReadableStream}
     * @private
     */
    _gpfWScriptBinReadStream = _gpfDefine("WScriptBinaryReadStream",
        _gpfWScriptBinStream, {

        "[Class]": [gpf.$InterfaceImplement(_gpfI.IReadableStream)],

        public: {

            /**
             * @param {String} path
             * @constructor
             */
            constructor: function (path) {
                this._super(path, true);
            },

            /**
             * @inheritdoc gpf.interfaces.IReadableStream:read
             */
            read: function (size, eventsHandler) {
                var buffer = this._adoStream.ReadText();
                _gpfIgnore(size);
                if (null === buffer) {
                    _gpfEventsFire.apply(this, [_GPF_EVENT_END_OF_DATA, {},
                        eventsHandler]);
                } else {
                    _gpfEventsFire.apply(this, [
                        _GPF_EVENT_DATA,
                        {
                            buffer: buffer
                        },
                        eventsHandler
                    ]);
                }
            }

        }

    }),

    /**
     * Convert bytes into its code page 437 representation
     *
     * @param {Number} value
     * @returns {String}
     * @private
     */
    _gpfByteTo437 = function (value) {
        return _gpf437chars[value];
    },

    /**
     * Binary stream writer
     *
     * @class {WScriptBinaryWriteStream}
     * @implements {gpf.interfaces.IWritableStream}
     * @private
     */
    _gpfWScriptBinWriteStream = _gpfDefine("WScriptBinaryWriteStream",
        _gpfWScriptBinStream, {

        "[Class]": [gpf.$InterfaceImplement(_gpfI.IWritableStream)],

        public: {

            /**
             * @inheritdoc gpf.interfaces.IWritableStream:write
             */
            write: function (buffer, eventsHandler) {
                var buffer437 = buffer.map(_gpfByteTo437);
                this._adoStream.WriteText(buffer437.join(""));
                _gpfEventsFire.apply(this, [_GPF_EVENT_READY, {},
                    eventsHandler]);
            },

            /**
             * @inheritdoc WScriptBinaryStream:close
             */
            close: function () {
                this._adoStream.SaveToFile(this._path,
                    2/*adSaveCreateOverWrite*/);
                this._super();
            }

        }

    });

_gpfDefine("gpf.fs.WScriptFileStorage", {

    public: {

        constructor: function () {
            if (!_gpfMsFSO) {
                _gpfMsFSO = new ActiveXObject("Scripting.FileSystemObject");
            }
        },

        //region IFileStorage

        /**
         * @inheritdoc IFileStorage:getInfo
         */
        getInfo: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            if (_gpfMsFSO.FileExists(path)) {
                var file = _gpfMsFSO.GetFile(path);
                _gpfEventsFire.apply(null, [
                    _GPF_EVENT_READY,
                    {
                        info: _gpfFsoObjToInfo(file, _GPF_FS_TYPE_FILE)
                    },
                    eventsHandler
                ]);

            } else if (_gpfMsFSO.FolderExists(path)) {
                var folder = _gpfMsFSO.GetFolder(path);
                _gpfEventsFire.apply(null, [
                    _GPF_EVENT_READY,
                    {
                        info: _gpfFsoObjToInfo(folder, _GPF_FS_TYPE_DIRECTORY)
                    },
                    eventsHandler
                ]);

            } else {
                _gpfEventsFire.apply(null, [
                    _GPF_EVENT_READY,
                    {
                        info: {
                            type: _GPF_FS_TYPE_NOT_FOUND
                        }
                    },
                    eventsHandler
                ]);
            }
        },

        /**
         * @inheritdoc IFileStorage:readAsBinaryStream
         */
        readAsBinaryStream: function (path, eventsHandler) {
            _gpfEventsFire.apply(null, [
                _GPF_EVENT_READY,
                {
                    stream: new _gpfWScriptBinReadStream(path)
                },
                eventsHandler
            ]);
        },

        /**
         * @inheritdoc IFileStorage:writeAsBinaryStream
         */
        writeAsBinaryStream: function (path, eventsHandler) {
            _gpfEventsFire.apply(null, [
                _GPF_EVENT_READY,
                {
                    stream: new _gpfWScriptBinWriteStream(path)
                },
                eventsHandler
            ]);
        },

        /**
         * @inheritdoc IFileStorage:close
         */
        close: function (stream, eventsHandler) {
            if (stream instanceof _gpfWScriptBinStream) {
                stream.close();
                _gpfEventsFire.apply(stream, [
                    _GPF_EVENT_READY,
                    {},
                    eventsHandler
                ]);
            } else {
                throw gpf.Error.InvalidParameter();
            }
        },

        /**
         * @inheritdoc IFileStorage:explore
         */
        explore: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            var result = [],
                folder,
                fsoEnum;
            if (_gpfMsFSO.FolderExists(path)) {
                folder = _gpfMsFSO.GetFolder(path);
                // files
                fsoEnum = new Enumerator(folder.Files);
                for(; fsoEnum.atEnd(); fsoEnum.moveNext()) {
                    result.push(_gpfFsoObjToInfo(fsoEnum.item(),
                        _GPF_FS_TYPE_FILE));
                }
                // folders
                fsoEnum = new Enumerator(folder.SubFolders);
                for(; fsoEnum.atEnd(); fsoEnum.moveNext()) {
                    result.push(_gpfFsoObjToInfo(fsoEnum.item(),
                        _GPF_FS_TYPE_DIRECTORY));
                }
            }
            _gpfEventsFire.apply(null, [
                _GPF_EVENT_READY,
                {
                    enumerator: _gpfArrayEnumerator(result)
                },
                eventsHandler
            ]);
        },

        /**
         * @inheritdoc IFileStorage:createFolder
         */
        createFolder: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            _gpfMsFSO.CreateFolder(path);
            _gpfEventsFire.apply(null, [_GPF_EVENT_READY, {}, eventsHandler]);
        },

        /**
         * @inheritdoc IFileStorage:deleteFile
         */
        deleteFile: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            _gpfMsFSO.DeleteFile(path, true);
            _gpfEventsFire.apply(null, [_GPF_EVENT_READY, {}, eventsHandler]);
        },

        /**
         * @inheritdoc IFileStorage:deleteFolder
         */
        deleteFolder: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            _gpfMsFSO.DeleteFolder(path, true);
            _gpfEventsFire.apply(null, [_GPF_EVENT_READY, {}, eventsHandler]);
        }

        //endregion

    }

});

if ("wscript" === _gpfHost) {

    /**
     * @inheritdoc gpf.fs#host
     * WScript version
     */
    gpf.fs.host = function () {
        if (!_gpfWScriptFileStorage) {
            _gpfWScriptFileStorage = new gpf.fs.WScriptFileStorage();
        }
        return _gpfWScriptFileStorage;
    };

}
<commit_msg>Will use FSO TextStream<commit_after>/*#ifndef(UMD)*/
"use strict";
/*global _GPF_EVENT_DATA*/ // gpf.events.EVENT_DATA
/*global _GPF_EVENT_END_OF_DATA*/ // gpf.events.EVENT_END_OF_DATA
/*global _GPF_EVENT_READY*/ // gpf.events.EVENT_READY
/*global _GPF_FS_TYPE_DIRECTORY*/ // _GPF_FS_TYPE_DIRECTORY
/*global _GPF_FS_TYPE_FILE*/ // _GPF_FS_TYPE_FILE
/*global _GPF_FS_TYPE_NOT_FOUND*/ // _GPF_FS_TYPE_NOT_FOUND
/*global _gpfArrayEnumerator*/ // Create an IEnumerator from an array
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfEventsFire*/ // gpf.events.fire (internal, parameters must match)
/*global _gpfHost*/ // Host type
/*global _gpfI*/ // gpf.interfaces
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*global _gpfMsFSO:true*/ // Scripting.FileSystemObject activeX
/*global _gpfPathDecompose*/ // Normalize path and returns an array of parts
/*global _gpfPathNormalize*/ // Normalize path
// /*#endif*/

var
    /**
     * @type {gpf.fs.WScriptFileStorage}
     * @private
     */
    _gpfWScriptFileStorage,

    /**
     * Translate WScript object info into a file info
     *
     * @param {Scripting.FileSystemObject.File
     * |Scripting.FileSystemObject.Folder} fsoObj
     * @param {Number} type
     * @private
     */
    _gpfFsoObjToInfo = function (fsoObj, type) {
        return {
            type: type,
            fileName: fsoObj.Name.toLowerCase(),
            filePath: _gpfPathNormalize(fsoObj.Path),
            size: fsoObj.Size,
            createdDateTime: fsoObj.DateCreated,
            modifiedDateTime: fsoObj.DateLastModified
        };
    },

    /**
     * String representing character & binary mapping
     *
     * @type {String|String[]}
     * @private
     */
    _gpf437chars = "C7|FC|E9|E2|E4|E0|E5|E7|EA|EB|E8|EF|EE|EC|C4|C5|C9|E6|C6" +
        "|F4|F6|F2|FB|F9|FF|D6|DC|A2|A3|A5|20A7|192|E1|ED|F3|FA|F1|D1|AA|BA" +
        "|BF|2310|AC|BD|BC|A1|AB|BB|2591|2592|2593|2502|2524|2561|2562|2556" +
        "|2555|2563|2551|2557|255D|255C|255B|2510|2514|2534|252C|251C|2500" +
        "|253C|255E|255F|255A|2554|2569|2566|2560|2550|256C|2567|2568|2564" +
        "|2565|2559|2558|2552|2553|256B|256A|2518|250C|2588|2584|258C|2590" +
        "|2580|3B1|DF|393|3C0|3A3|3C3|B5|3C4|3A6|398|3A9|3B4|221E|3C6|3B5" +
        "|2229|2261|B1|2265|2264|2320|2321|F7|2248|B0|2219|B7|221A|207F" +
        "|B2|25A0|A0",

    /**
     * Convert hexadecimal character code into a character
     *
     * @param {String} hexa Hexadecimal character code
     * @returns {String} Character
     * @private
     */
    _gpfCharCodeFromHexa = function (hexa) {
        return String.fromCharCode(parseInt(hexa, 16));
    },

    /**
     * Build the 256 characters composing the code page 437
     *
     * @private
     */
    _gpfBuild437chars = function () {
        var asciiChars = [],
            idx;
        for (idx = 128; idx > 0;) {
            asciiChars.unshift(String.fromCharCode(--idx));
        }
        _gpf437chars = asciiChars.concat(_gpf437chars
            .split("|")
            .map(_gpfCharCodeFromHexa)
        );
    },

    /**
     * Binary stream base class
     *
     * @class {WScriptBinaryStream}
     * @private
     */
    _gpfWScriptBinStream = _gpfDefine("WScriptBinaryStream", {

        public: {

            /**
             * @param {String} path
             * @param {Boolean} [load=false] load load from file
             * @constructor
             */
            constructor: function (path, load) {
                this._path = _gpfPathDecompose(path).join("\\");
                var stream = this._adoStream
                    = new ActiveXObject("ADODB.Stream");
                // https://en.wikipedia.org/wiki/Code_page_437
                stream.Open();
                if (load) {
                    stream.LoadFromFile(this._path);
                }
                stream.Position = 0;
                stream.Type = 2; /*adTypeText*/
                stream.CharSet = "437";
                if (256 !== _gpf437chars.length) {
                    _gpfBuild437chars();
                }
            },

            /**
             * Close the stream
             */
            close: function () {
                this._adoStream.Close();
            }

        },

        protected: {

            /**
             * File path
             * @type {String}
             */
            _path: "",

            /**
             * ADODB.Stream
             * @type {Object}
             */
            _adoStream: null

        }

    }),

    /**
     * Binary stream reader
     *
     * @class {WScriptBinaryReadStream}
     * @implements {gpf.interfaces.IReadableStream}
     * @private
     */
    _gpfWScriptBinReadStream = _gpfDefine("WScriptBinaryReadStream",
        _gpfWScriptBinStream, {

        "[Class]": [gpf.$InterfaceImplement(_gpfI.IReadableStream)],

        public: {

            /**
             * @param {String} path
             * @constructor
             */
            constructor: function (path) {
                this._super(path, true);
            },

            /**
             * @inheritdoc gpf.interfaces.IReadableStream:read
             */
            read: function (size, eventsHandler) {
                var buffer = this._adoStream.ReadText();
                _gpfIgnore(size);
                if (null === buffer) {
                    _gpfEventsFire.apply(this, [_GPF_EVENT_END_OF_DATA, {},
                        eventsHandler]);
                } else {
                    _gpfEventsFire.apply(this, [
                        _GPF_EVENT_DATA,
                        {
                            buffer: buffer
                        },
                        eventsHandler
                    ]);
                }
            }

        }

    }),

    /**
     * Convert bytes into its code page 437 representation
     *
     * @param {Number} value
     * @returns {String}
     * @private
     */
    _gpfByteTo437 = function (value) {
        return _gpf437chars[value];
    },

    /**
     * Binary stream writer
     *
     * @class {WScriptBinaryWriteStream}
     * @implements {gpf.interfaces.IWritableStream}
     * @private
     */
    _gpfWScriptBinWriteStream = _gpfDefine("WScriptBinaryWriteStream",
        _gpfWScriptBinStream, {

        "[Class]": [gpf.$InterfaceImplement(_gpfI.IWritableStream)],

        public: {

            /**
             * @inheritdoc gpf.interfaces.IWritableStream:write
             */
            write: function (buffer, eventsHandler) {
                var buffer437 = buffer.map(_gpfByteTo437);
                this._adoStream.WriteText(buffer437.join(""));
                _gpfEventsFire.apply(this, [_GPF_EVENT_READY, {},
                    eventsHandler]);
            },

            /**
             * @inheritdoc WScriptBinaryStream:close
             */
            close: function () {
                this._adoStream.SaveToFile(this._path,
                    2/*adSaveCreateOverWrite*/);
                this._super();
            }

        }

    });

    /*
     * TODO will use TextStream, need only some mappings with it:
     * 8364: 128
     * 8218: 130
     * 402: 131
     * 8222: 132
     * 8230: 133
     * 8224: 134
     * 8225: 135
     * 710: 136
     * 8240: 137
     * 352: 138
     * 8249: 139
     * 338: 140
     * 381: 142
     * 8216: 145
     * 8217: 146
     * 8220: 147
     * 8221: 148
     * 8226: 149
     * 8211: 150
     * 8212: 151
     * 732: 152
     * 8482: 153
     * 353: 154
     * 8250: 155
     * 339: 156
     * 382: 158
     * 376: 159
     */

_gpfDefine("gpf.fs.WScriptFileStorage", {

    public: {

        constructor: function () {
            if (!_gpfMsFSO) {
                _gpfMsFSO = new ActiveXObject("Scripting.FileSystemObject");
            }
        },

        //region IFileStorage

        /**
         * @inheritdoc IFileStorage:getInfo
         */
        getInfo: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            if (_gpfMsFSO.FileExists(path)) {
                var file = _gpfMsFSO.GetFile(path);
                _gpfEventsFire.apply(null, [
                    _GPF_EVENT_READY,
                    {
                        info: _gpfFsoObjToInfo(file, _GPF_FS_TYPE_FILE)
                    },
                    eventsHandler
                ]);

            } else if (_gpfMsFSO.FolderExists(path)) {
                var folder = _gpfMsFSO.GetFolder(path);
                _gpfEventsFire.apply(null, [
                    _GPF_EVENT_READY,
                    {
                        info: _gpfFsoObjToInfo(folder, _GPF_FS_TYPE_DIRECTORY)
                    },
                    eventsHandler
                ]);

            } else {
                _gpfEventsFire.apply(null, [
                    _GPF_EVENT_READY,
                    {
                        info: {
                            type: _GPF_FS_TYPE_NOT_FOUND
                        }
                    },
                    eventsHandler
                ]);
            }
        },

        /**
         * @inheritdoc IFileStorage:readAsBinaryStream
         */
        readAsBinaryStream: function (path, eventsHandler) {
            _gpfEventsFire.apply(null, [
                _GPF_EVENT_READY,
                {
                    stream: new _gpfWScriptBinReadStream(path)
                },
                eventsHandler
            ]);
        },

        /**
         * @inheritdoc IFileStorage:writeAsBinaryStream
         */
        writeAsBinaryStream: function (path, eventsHandler) {
            _gpfEventsFire.apply(null, [
                _GPF_EVENT_READY,
                {
                    stream: new _gpfWScriptBinWriteStream(path)
                },
                eventsHandler
            ]);
        },

        /**
         * @inheritdoc IFileStorage:close
         */
        close: function (stream, eventsHandler) {
            if (stream instanceof _gpfWScriptBinStream) {
                stream.close();
                _gpfEventsFire.apply(stream, [
                    _GPF_EVENT_READY,
                    {},
                    eventsHandler
                ]);
            } else {
                throw gpf.Error.InvalidParameter();
            }
        },

        /**
         * @inheritdoc IFileStorage:explore
         */
        explore: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            var result = [],
                folder,
                fsoEnum;
            if (_gpfMsFSO.FolderExists(path)) {
                folder = _gpfMsFSO.GetFolder(path);
                // files
                fsoEnum = new Enumerator(folder.Files);
                for(; fsoEnum.atEnd(); fsoEnum.moveNext()) {
                    result.push(_gpfFsoObjToInfo(fsoEnum.item(),
                        _GPF_FS_TYPE_FILE));
                }
                // folders
                fsoEnum = new Enumerator(folder.SubFolders);
                for(; fsoEnum.atEnd(); fsoEnum.moveNext()) {
                    result.push(_gpfFsoObjToInfo(fsoEnum.item(),
                        _GPF_FS_TYPE_DIRECTORY));
                }
            }
            _gpfEventsFire.apply(null, [
                _GPF_EVENT_READY,
                {
                    enumerator: _gpfArrayEnumerator(result)
                },
                eventsHandler
            ]);
        },

        /**
         * @inheritdoc IFileStorage:createFolder
         */
        createFolder: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            _gpfMsFSO.CreateFolder(path);
            _gpfEventsFire.apply(null, [_GPF_EVENT_READY, {}, eventsHandler]);
        },

        /**
         * @inheritdoc IFileStorage:deleteFile
         */
        deleteFile: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            _gpfMsFSO.DeleteFile(path, true);
            _gpfEventsFire.apply(null, [_GPF_EVENT_READY, {}, eventsHandler]);
        },

        /**
         * @inheritdoc IFileStorage:deleteFolder
         */
        deleteFolder: function (path, eventsHandler) {
            path = _gpfPathDecompose(path).join("\\");
            _gpfMsFSO.DeleteFolder(path, true);
            _gpfEventsFire.apply(null, [_GPF_EVENT_READY, {}, eventsHandler]);
        }

        //endregion

    }

});

if ("wscript" === _gpfHost) {

    /**
     * @inheritdoc gpf.fs#host
     * WScript version
     */
    gpf.fs.host = function () {
        if (!_gpfWScriptFileStorage) {
            _gpfWScriptFileStorage = new gpf.fs.WScriptFileStorage();
        }
        return _gpfWScriptFileStorage;
    };

}
<|endoftext|>


<commit_before>import stringify from 'stabilize'
import omit from 'lodash/omit'
import mapValues from 'lodash/mapValues'

export function prepareStudentForSave(student) {
	student = Object.assign({}, student)
	student = omit(student, ['areas', 'canGraduate', 'fulfilled'])
	student.schedules = mapValues(student.schedules, s =>
		omit(s, ['courses', 'conflicts', 'hasConflict']),
	)
	return student
}

export function encodeStudent(student) {
	return encodeURIComponent(stringify(prepareStudentForSave(student)))
}
<commit_msg>rework encodeStudent to use object rest/spread<commit_after>import stringify from 'stabilize'
import mapValues from 'lodash/mapValues'

export function prepareStudentForSave(initialStudent) {
	// we're going to use object rest/spread to "omit" values from the objects

	let {
		areas: _areas,
		canGraduate: _can,
		fulfilled: _fulfilled,
		...student
	} = initialStudent

	student.schedules = mapValues(
		student.schedules,
		({courses: _courses, conflicts: _conflicts, hasConflict: _has, ...s}) =>
			s,
	)

	return student
}

export function encodeStudent(student) {
	return encodeURIComponent(stringify(prepareStudentForSave(student)))
}
<|endoftext|>


<commit_before>/*
 * Logspace
 * Copyright (c) 2015 Indoqa Software Design und Beratung GmbH. All rights reserved.
 * This program and the accompanying materials are made available under the terms of
 * the Eclipse Public License Version 1.0, which accompanies this distribution and
 * is available at http://www.eclipse.org/legal/epl-v10.html.
 */
import React from 'react'
import Immutable from 'immutable'
import c3 from 'c3'
import classnames from 'classnames'
import Halogen from 'halogen'
import moment from 'moment'
import shallowEqual from 'react-pure-render/shallowEqual';

import Component from '../components/component.react'
import debounceFunc from '../../lib/debounce'

import {units} from '../time-window/constants'

import {onResultRefreshed} from './actions'

require ('./result-chart.styl')

const ComponentState = Immutable.fromJS({
  loadingCss: {
    'loading' : true,
    'active' : false
  }
})

export default class Chart extends Component {

  constructor(props) {
    super(props)

    this.state = {
      localState: ComponentState
    }

    this.prevType = props.chartType
  }

  componentWillReceiveProps(nextProps) {
    this.rememberPreviousChartType(this.props.chartType)

    if (nextProps.result.get("loading")) {
      this.toggleLoading(true)
      return
    }

    this.toggleLoading(false)
  }

  componentDidUpdate() {
    const messageElement = document.getElementById('message')
    messageElement.innerHTML = ''

    if (this.props.result.get("loading")) {
      return
    }

    if (this.props.result.get("error")) {
      this.clearChart()
      messageElement.innerHTML = this.props.result.get("errorStatus") + '
' + this.props.result.get("errorText") + ''
      return
    }

    if (this.props.result.get("empty")) {
      this.clearChart()
      messageElement.innerHTML = 'Empty Chart
Add at least one time timeseries'
      return
    }

    if (this.prevType != this.props.chartType) {
      this.transform(this.props.chartType)
      this.prevType = this.props.chartType
      return
    }

    this.clearChart()

    const chartData = this.props.result.get("chartData").toJS()

    if (this.isEmpty(chartData)) {
      this.clearChart()
      messageElement.innerHTML = 'Empty Chart
No data found in selected time window'
      return
    }

    this.chart = c3.generate(this.chartOptions(chartData))
    this.originalColumns = chartData.originalColumns
  }

  isEmpty(chartData) {
    const originalColumns = chartData.originalColumns
    let count = 0

    for (var i = 0; i < originalColumns.length; i++) {
      let originalColumn = originalColumns[i]
      count = count + originalColumn.length - 1
    }

    return count == 0
  }

  toggleLoading(show) {
    this.setState({
      localState: this.state.localState.updateIn(['loadingCss', 'active'], () => { return show })
    })
  }

  rememberPreviousChartType(type) {
    this.prevType = type
  }

  clearChart() {
    if (this.chart) {
      try { this.chart.destroy() } catch(e) {}
    }
  }

  calculateChartSize() {
    const windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
    const windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)

    const minWindowWidth = 1024
    const minWindowHeight = windowHeight - 0

    const headerheight = 40
    const sidebarWidth = 250
    const chartPadding = 30 * 2
    const heightWidthRatio = 0.45

    const width = Math.max(windowWidth, minWindowWidth) - sidebarWidth - chartPadding
    const height = Math.min(windowHeight, minWindowHeight) - chartPadding - headerheight

    return {
      width : width,
      height : height
    }
  }

  resizeChart() {
    this.chart.resize(this.calculateChartSize())
  }

  transform(type) {
    this.chart.transform(type)
  }

  formatXAxis(index) {
    const date = this.props.result.get('chartData').get('xvalues').get(index)

    if (!date) {
      return
    }

    const unit = this.props.result.get('gap').get('unit')

    switch (unit.get('id')) {
      case units.get('second').get('id'):
        return date.format('HH:mm:ss')

      case units.get('minute').get('id'):
        return date.format('HH:mm')

      case units.get('hour').get('id'):
        return date.format('HH:00')

      case units.get('day').get('id'):
        return date.format('DD.MM.')

      case units.get('week').get('id'):
        return date.format('DD.MM.')

      case units.get('month').get('id'):
        return date.format('MMMM')

      case units.get('year').get('id'):
        return date.format('YYYY')
    }

    return unit
  }

  formatXTooltip(index) {
    const date = this.props.result.get('chartData').get('xvalues').get(index)

    if (!date) {
      return
    }

    const unit = this.props.result.get('gap').get('unit')

    switch (unit.get('id')) {
      case units.get('second').get('id'):
        return date.format('dd DD.MM.YYYY - HH:mm:ss')

      case units.get('minute').get('id'):
        return date.format('dd DD.MM.YYYY - HH:mm')

      case units.get('hour').get('id'):
        return date.format('dd DD.MM.YYYY - HH:00')

      case units.get('day').get('id'):
        return date.format('dd DD.MM.YYYY')

      case units.get('week').get('id'):
        return date.format('dd DD.MM.YYYY')

      case units.get('month').get('id'):
        return date.format('MMMM YYYY')

      case units.get('year').get('id'):
        return date.format('YYYY')
    }

    return unit
  }

  formatYTooltip(value, ratio, id, index) {
    return this.originalColumns[id][index]
  }

  render() {
    return (

            <Halogen.PulseLoader color={'#ddfcff'} size={'50px'} />

    )
  }

  chartOptions(chartData) {
    const chartSize = this.calculateChartSize()
    const me = this

    const debouncedChartResize = debounceFunc(this.resizeChart.bind(me), 350)
    const formatXAxisCallback = this.formatXAxis.bind(me)
    const formatXTooltipCallback = this.formatXTooltip.bind(me)
    const formatYTooltipCallback = this.formatYTooltip.bind(me)

    const defaultType = this.props.chartType

    return {
      data: {
          x: 'x',
          type: defaultType,
          colors: chartData.colors,
          columns: chartData.columns,
          names: chartData.names,
          axes: chartData.axes
        },
      axis: {
        x: {
          type: 'category',
          tick: {
            count: this.getMaxTicks(chartData),
            fit: false,
            height: 130,
            rotate: 0,
            format: formatXAxisCallback
          }
        },
        y: {
          show: true,
          padding: 0,
          min: chartData.axisRanges.min.y,
          max: chartData.axisRanges.max.y
        },
        y2: this.getY2Options(chartData)
      },
      grid: {
        x: {
          show: false
        },
        y: {
          show: false
        }
      },
      tooltip: {
        format: {
          title: formatXTooltipCallback,
          value: formatYTooltipCallback
        }
      },
      legend: {
        show: false
      },
      point: {
        r: 2
      },
      size: {
        height: chartSize.height,
        width: chartSize.width
      },
      onresized: debouncedChartResize
    }
  }

  getMaxTicks(chartData) {
    if (chartData.xvalues.length < 10) {
      return chartData.xvalues.length
    }

    return 10
  }

  getY2Options(chartData) {
    if (chartData.axisRanges.min.y2 == 0 && chartData.axisRanges.max.y2 == 0) {
      return {
        show: false
      }
    }

    return {
      show: true,
      padding: 0,
      min: chartData.axisRanges.min.y2,
      max: chartData.axisRanges.max.y2
    }
  }
}
<commit_msg>fix empty chart check<commit_after>/*
 * Logspace
 * Copyright (c) 2015 Indoqa Software Design und Beratung GmbH. All rights reserved.
 * This program and the accompanying materials are made available under the terms of
 * the Eclipse Public License Version 1.0, which accompanies this distribution and
 * is available at http://www.eclipse.org/legal/epl-v10.html.
 */
import React from 'react'
import Immutable from 'immutable'
import c3 from 'c3'
import classnames from 'classnames'
import Halogen from 'halogen'
import moment from 'moment'
import shallowEqual from 'react-pure-render/shallowEqual';

import Component from '../components/component.react'
import debounceFunc from '../../lib/debounce'

import {units} from '../time-window/constants'

import {onResultRefreshed} from './actions'

require ('./result-chart.styl')

const ComponentState = Immutable.fromJS({
  loadingCss: {
    'loading' : true,
    'active' : false
  }
})

export default class Chart extends Component {

  constructor(props) {
    super(props)

    this.state = {
      localState: ComponentState
    }

    this.prevType = props.chartType
  }

  componentWillReceiveProps(nextProps) {
    this.rememberPreviousChartType(this.props.chartType)

    if (nextProps.result.get("loading")) {
      this.toggleLoading(true)
      return
    }

    this.toggleLoading(false)
  }

  componentDidUpdate() {
    const messageElement = document.getElementById('message')
    messageElement.innerHTML = ''

    if (this.props.result.get("loading")) {
      return
    }

    if (this.props.result.get("error")) {
      this.clearChart()
      messageElement.innerHTML = this.props.result.get("errorStatus") + '
' + this.props.result.get("errorText") + ''
      return
    }

    if (this.props.result.get("empty")) {
      this.clearChart()
      messageElement.innerHTML = 'Empty Chart
Add at least one time timeseries'
      return
    }

    if (this.prevType != this.props.chartType) {
      this.transform(this.props.chartType)
      this.prevType = this.props.chartType
      return
    }

    this.clearChart()

    const chartData = this.props.result.get("chartData").toJS()

    if (this.isEmpty(chartData)) {
      this.clearChart()
      messageElement.innerHTML = 'Empty Chart
No data found in selected time window'
      return
    }

    this.chart = c3.generate(this.chartOptions(chartData))
    this.originalColumns = chartData.originalColumns
  }

  isEmpty(chartData) {
    const originalColumns = chartData.originalColumns
    let count = 0

    console.log(originalColumns)

    for(var key in originalColumns) {
     var originalColumn = originalColumns[key];
      console.log(originalColumn)
      count = count + originalColumn.length
    }

    return count == 0
  }

  toggleLoading(show) {
    this.setState({
      localState: this.state.localState.updateIn(['loadingCss', 'active'], () => { return show })
    })
  }

  rememberPreviousChartType(type) {
    this.prevType = type
  }

  clearChart() {
    if (this.chart) {
      try { this.chart.destroy() } catch(e) {}
    }
  }

  calculateChartSize() {
    const windowWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
    const windowHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)

    const minWindowWidth = 1024
    const minWindowHeight = windowHeight - 0

    const headerheight = 40
    const sidebarWidth = 250
    const chartPadding = 30 * 2
    const heightWidthRatio = 0.45

    const width = Math.max(windowWidth, minWindowWidth) - sidebarWidth - chartPadding
    const height = Math.min(windowHeight, minWindowHeight) - chartPadding - headerheight

    return {
      width : width,
      height : height
    }
  }

  resizeChart() {
    this.chart.resize(this.calculateChartSize())
  }

  transform(type) {
    this.chart.transform(type)
  }

  formatXAxis(index) {
    const date = this.props.result.get('chartData').get('xvalues').get(index)

    if (!date) {
      return
    }

    const unit = this.props.result.get('gap').get('unit')

    switch (unit.get('id')) {
      case units.get('second').get('id'):
        return date.format('HH:mm:ss')

      case units.get('minute').get('id'):
        return date.format('HH:mm')

      case units.get('hour').get('id'):
        return date.format('HH:00')

      case units.get('day').get('id'):
        return date.format('DD.MM.')

      case units.get('week').get('id'):
        return date.format('DD.MM.')

      case units.get('month').get('id'):
        return date.format('MMMM')

      case units.get('year').get('id'):
        return date.format('YYYY')
    }

    return unit
  }

  formatXTooltip(index) {
    const date = this.props.result.get('chartData').get('xvalues').get(index)

    if (!date) {
      return
    }

    const unit = this.props.result.get('gap').get('unit')

    switch (unit.get('id')) {
      case units.get('second').get('id'):
        return date.format('dd DD.MM.YYYY - HH:mm:ss')

      case units.get('minute').get('id'):
        return date.format('dd DD.MM.YYYY - HH:mm')

      case units.get('hour').get('id'):
        return date.format('dd DD.MM.YYYY - HH:00')

      case units.get('day').get('id'):
        return date.format('dd DD.MM.YYYY')

      case units.get('week').get('id'):
        return date.format('dd DD.MM.YYYY')

      case units.get('month').get('id'):
        return date.format('MMMM YYYY')

      case units.get('year').get('id'):
        return date.format('YYYY')
    }

    return unit
  }

  formatYTooltip(value, ratio, id, index) {
    return this.originalColumns[id][index]
  }

  render() {
    return (

            <Halogen.PulseLoader color={'#ddfcff'} size={'50px'} />

    )
  }

  chartOptions(chartData) {
    const chartSize = this.calculateChartSize()
    const me = this

    const debouncedChartResize = debounceFunc(this.resizeChart.bind(me), 350)
    const formatXAxisCallback = this.formatXAxis.bind(me)
    const formatXTooltipCallback = this.formatXTooltip.bind(me)
    const formatYTooltipCallback = this.formatYTooltip.bind(me)

    const defaultType = this.props.chartType

    return {
      data: {
          x: 'x',
          type: defaultType,
          colors: chartData.colors,
          columns: chartData.columns,
          names: chartData.names,
          axes: chartData.axes
        },
      axis: {
        x: {
          type: 'category',
          tick: {
            count: this.getMaxTicks(chartData),
            fit: false,
            height: 130,
            rotate: 0,
            format: formatXAxisCallback
          }
        },
        y: {
          show: true,
          padding: 0,
          min: chartData.axisRanges.min.y,
          max: chartData.axisRanges.max.y
        },
        y2: this.getY2Options(chartData)
      },
      grid: {
        x: {
          show: false
        },
        y: {
          show: false
        }
      },
      tooltip: {
        format: {
          title: formatXTooltipCallback,
          value: formatYTooltipCallback
        }
      },
      legend: {
        show: false
      },
      point: {
        r: 2
      },
      size: {
        height: chartSize.height,
        width: chartSize.width
      },
      onresized: debouncedChartResize
    }
  }

  getMaxTicks(chartData) {
    if (chartData.xvalues.length < 10) {
      return chartData.xvalues.length
    }

    return 10
  }

  getY2Options(chartData) {
    if (chartData.axisRanges.min.y2 == 0 && chartData.axisRanges.max.y2 == 0) {
      return {
        show: false
      }
    }

    return {
      show: true,
      padding: 0,
      min: chartData.axisRanges.min.y2,
      max: chartData.axisRanges.max.y2
    }
  }
}
<|endoftext|>


<commit_before>
define([
    'flight/lib/component',
    'flight/lib/registry',
    'data',
    'tpl!./list',
    'tpl!./item',
    'tpl!util/alert',
    'util/video/scrubber',
    'util/jquery.withinScrollable',
    'util/jquery.ui.draggable.multiselect'
], function(defineComponent, registry, appData, template, vertexTemplate, alertTemplate, VideoScrubber) {
    'use strict';

    return defineComponent(List);

    function List() {

        this.defaultAttrs({
            itemSelector: '.vertex-item',
            infiniteScrolling: false
        });

        this.stateForVertex = function(vertex) {
            var inWorkspace = appData.inWorkspace(vertex);
            return {
                inGraph: inWorkspace,
                inMap: inWorkspace && !!(
                        vertex.properties.geoLocation ||
                        (vertex.location || (vertex.locations && vertex.locations.length)) ||
                        (vertex.properties.latitude && vertex.properties.longitude)
                )
            };
        };

        this.classNameMapForVertices = function(vertices) {
            var self = this,
                classNamesForVertex = {};

            vertices.forEach(function(v) {

                // Check if this vertex is in the graph/map
                self.classNameLookup[v.id] = (self.classNameLookup[v.id] || ('vId' + (++self.classNameIndex)));

                var classes = [self.classNameLookup[v.id]];

                var vertexState = self.stateForVertex(v);
                if ( vertexState.inGraph ) classes.push('graph-displayed');
                if ( vertexState.inMap ) classes.push('map-displayed');

                if (!v.imageSrcIsFromConcept) {
                    classes.push('has_preview');
                }

                classNamesForVertex[v.id] = classes.join(' ');
            });

            return classNamesForVertex;
        };

        this.after('initialize', function() {
            var self = this;

            this.classNameIndex = 0;
            this.classNameLookup = {};

            this.$node
                .addClass('vertex-list')
                .html(template({
                    vertices:this.attr.vertices,
                    infiniteScrolling: this.attr.infiniteScrolling && this.attr.total !== this.attr.vertices.length,
                    classNamesForVertex: this.classNameMapForVertices(this.attr.vertices)
                }));

            this.attachEvents();

            this.loadVisibleResultPreviews = _.debounce(this.loadVisibleResultPreviews.bind(this), 1000);
            this.loadVisibleResultPreviews();

            this.triggerInfiniteScrollRequest = _.debounce(this.triggerInfiniteScrollRequest.bind(this), 1000);
            this.triggerInfiniteScrollRequest();

            this.setupDraggables();

            this.onObjectsSelected(null, { edges:[], vertices:appData.selectedVertices});

            this.on('selectAll', this.onSelectAll);
            this.on('down', this.move);
            this.on('up', this.move);

            _.defer(function() {
                this.$node.scrollTop(0);
            }.bind(this))

        });

        this.move = function(e, data) {
            var previousSelected = this.$node.find('.active')[e.type === 'up' ? 'first' : 'last'](),
                moveTo = previousSelected[e.type === 'up' ? 'prev' : 'next']('.vertex-item');

            if (moveTo.length) {

                var selected = [];

                if (data.shiftKey) {
                    selected = selected.concat(appData.selectedVertices);
                    selected.push(appData.vertex(moveTo.data('vertexId')));
                } else {
                    selected.push(appData.vertex(moveTo.data('vertexId')));
                }

                this.trigger(document, 'defocusVertices');
                this.trigger('selectObjects', { vertices:selected });
            }
        };

        this.onSelectAll = function(e) {
            e.stopPropagation();

            var items = this.$node.find('.vertex-item').addClass('active');
            this.selectItems(items);
        };

        this.after('teardown', function() {
            this.$node.off('mouseenter mouseleave');
            this.scrollNode.off('scroll.vertexList');
            this.$node.empty();
        });

        this.attachEvents = function() {
            this.scrollNode = this.$node;
            while (this.scrollNode.length && this.scrollNode.css('overflow') !== 'auto') {
                this.scrollNode = this.scrollNode.parent();
            }
            this.scrollNode.on('scroll.vertexList', this.onResultsScroll.bind(this));

            this.$node.on('mouseenter mouseleave', '.vertex-item', this.onHoverItem.bind(this));

            this.on(document, 'verticesAdded', this.onVerticesUpdated);
            this.on(document, 'verticesUpdated', this.onVerticesUpdated);
            this.on(document, 'verticesDeleted', this.onVerticesDeleted);
            this.on(document, 'objectsSelected', this.onObjectsSelected);
            this.on(document, 'switchWorkspace', this.onWorkspaceClear);
            this.on(document, 'workspaceDeleted', this.onWorkspaceClear);
            this.on(document, 'workspaceLoaded', this.onWorkspaceLoaded);
            this.on('addInfiniteVertices', this.onAddInfiniteVertices);
        };

        this.setupDraggables = function() {
            this.applyDraggable(this.$node.find('a'));
            this.$node.droppable({ accept:'*', tolerance:'pointer' });
        };

        this.onHoverItem = function(evt) {
            if (this.disableHover === 'defocused') {
                return;
            } else if (this.disableHover) {
                this.disableHover = 'defocused';
                return this.trigger(document, 'defocusVertices');
            }

            var id = $(evt.target).closest('.vertex-item').data('vertexId');
            if (evt.type == 'mouseenter' && id) {
                this.trigger(document, 'focusVertices', { vertexIds:[id] });
            } else {
                this.trigger(document, 'defocusVertices');
            }
        };

        this.onResultsScroll = function(e) {
            if (!this.disableHover) {
                this.disableHover = true;
            }

            this.loadVisibleResultPreviews();

            if (this.attr.infiniteScrolling) {
                this.triggerInfiniteScrollRequest();
            }
        };

        this.triggerInfiniteScrollRequest = function() {
            if (!this.attr.infiniteScrolling) return;

            var loadingListElement = this.$node.find('.infinite-loading');

            if (this.scrollNode.length) {
                loadingListElement = loadingListElement.withinScrollable(this.scrollNode);
            }

            if (loadingListElement.length) {
                var data = { conceptType: this.attr.verticesConceptId };
                if (!this.offset) this.offset = this.attr.vertices.length;
                data.paging = {
                    offset: this.offset
                };
                this.trigger('infiniteScrollRequest', data);
            }
        };

        this.onAddInfiniteVertices = function(evt, data) {
            var loading = this.$node.find('.infinite-loading');

            if (!data.success) {
                loading.html(alertTemplate({ error: 'Error loading search results' }));
                this.attr.infiniteScrolling = false;
            } else if (data.vertices.length === 0) {
                loading.remove();
                this.attr.infiniteScrolling = false;
            } else {
                this.offset += data.vertices.length;
                var clsMap = this.classNameMapForVertices(data.vertices),
                    added = data.vertices.map(function(vertex) {
                        return vertexTemplate({
                            vertex:vertex,
                            classNamesForVertex: clsMap
                        });
                    });

                var lastItem = loading.prev();

                loading.before(added);

                var total = data.total || this.attr.total || 0;
                if (total === this.$node.find('.vertex-item').length) {
                    loading.remove();
                    this.attr.infiniteScrolling = false;
                } else {
                    this.triggerInfiniteScrollRequest();
                }

                this.loadVisibleResultPreviews();

                this.applyDraggable(this.$node.find('a'));
            }
        };

        this.loadVisibleResultPreviews = function() {
            var self = this;

            this.disableHover = false;

            var lisVisible = this.$node.find('.nav-list').children('li');
            if (this.scrollNode.length) {
                lisVisible = lisVisible.withinScrollable(this.scrollNode);
            }

            lisVisible.each(function() {
                var li = $(this),
                    vertex = appData.vertex(li.data('vertexId'));

                if (vertex && !vertex.imageSrcIsFromConcept && !li.data('previewLoaded')) {

                    var preview = li.data('previewLoaded', true)
                                    .find('.preview');

                    if (vertex.imageFramesSrc) {
                        VideoScrubber.attachTo(preview, {
                            posterFrameUrl: vertex.imageSrc,
                            videoPreviewImageUrl: vertex.imageFramesSrc
                        });
                    } else {
                        preview.html("<img src='" + vertex.imageSrc + "' />");
                    }
                }
            });
        };

        this.applyDraggable = function(el) {
            var self = this;

            el.draggable({
                helper:'clone',
                appendTo: 'body',
                revert: 'invalid',
                revertDuration: 250,
                scroll: false,
                zIndex: 100,
                distance: 10,
                multi: true,
                otherDraggablesClass: 'vertex-dragging',
                start: function(ev, ui) {
                    $(ui.helper).addClass('vertex-dragging');
                },
                selection: function(ev, ui) {
                    self.selectItems(ui.selected);
                }
            });
        };

        this.selectItems = function(items) {
            var vertices = appData.vertices(items.map(function() {
                    return $(this).data('vertexId');
                }).toArray());

            if (vertices.length > 1) {
                vertices.forEach (function (vertex) {
                    vertex.workspace = {
                        selected: true
                    };
                });
            }
            this.trigger(document, 'defocusVertices');
            this.trigger('selectObjects', { vertices:vertices });
        };

        this.onWorkspaceLoaded = function(evt, workspace) {
            this.onVerticesUpdated(evt, workspace.data || {});
        };

        // Track changes to vertices so we display the "Displayed in Graph" icon
        // in search results
        this.toggleItemIcons = function(id, data) {
            this.$node
                .find('li.' + this.classNameLookup[id])
                .toggleClass('graph-displayed', data.inGraph)
                .toggleClass('map-displayed', data.inMap);
        };

        // Switching workspaces should clear the icon state and vertices
        this.onWorkspaceClear = function() {
            this.$node.find('li.graph-displayed').removeClass('graph-displayed');
            this.$node.find('li.map-displayed').removeClass('map-displayed');
        };

        this.onVerticesUpdated = function(event, data) {
            var self = this;
            (data.vertices || []).forEach(function(vertex) {
                self.toggleItemIcons(vertex.id, self.stateForVertex(vertex));
                var currentAnchor = self.$node.find('li.' + self.classNameLookup[vertex.id]).children('a'),
                    newAnchor = $(vertexTemplate({
                        vertex: vertex,
                        classNamesForVertex: self.classNameMapForVertices([vertex]),
                    })).children('a'),
                    currentHtml = currentAnchor.html(),
                    src = vertex.imageSrc,
                    showImageSrc = src && !vertex.imageSrcIsFromConcept;

                if (currentAnchor.length) {
                    if (showImageSrc) {
                        $('<img/>').attr('src', src).appendTo(newAnchor.find('.preview'));
                    }

                    var newHtml = newAnchor.html();
                    if (currentAnchor.length && newHtml !== currentHtml) {
                        currentAnchor.html(newHtml).closest('.vertex-item').toggleClass('has_preview', showImageSrc);
                    }
                }
            });
        };

        this.onVerticesDeleted = function(event, data) {
            var self = this;
            (data.vertices || []).forEach(function(vertex) {
                self.toggleItemIcons(vertex.id, { inGraph:false, inMap:false });
            });
        };

        this.onObjectsSelected = function(event, data) {
            this.$node.find('.active').removeClass('active');

            var self = this,
                ids = _.chain(data.vertices)
                    .map(function(v) { return '.' + self.classNameLookup[v.id]; })
                    .value().join(',');

            $(ids).addClass('active');
        };
    }
});
<commit_msg>Fix errant vertices being selecting in search results when opening detail pane and clicking items<commit_after>
define([
    'flight/lib/component',
    'flight/lib/registry',
    'data',
    'tpl!./list',
    'tpl!./item',
    'tpl!util/alert',
    'util/video/scrubber',
    'util/jquery.withinScrollable',
    'util/jquery.ui.draggable.multiselect'
], function(defineComponent, registry, appData, template, vertexTemplate, alertTemplate, VideoScrubber) {
    'use strict';

    return defineComponent(List);

    function List() {

        this.defaultAttrs({
            itemSelector: '.vertex-item',
            infiniteScrolling: false
        });

        this.stateForVertex = function(vertex) {
            var inWorkspace = appData.inWorkspace(vertex);
            return {
                inGraph: inWorkspace,
                inMap: inWorkspace && !!(
                        vertex.properties.geoLocation ||
                        (vertex.location || (vertex.locations && vertex.locations.length)) ||
                        (vertex.properties.latitude && vertex.properties.longitude)
                )
            };
        };

        this.vertexIdToClassName = function(id) {
            var className = this.classNameLookup[id] = (this.classNameLookup[id] || ('vId' + (++this.classNameIndex)));
            return className;
        };

        this.classNameMapForVertices = function(vertices) {
            var self = this,
                classNamesForVertex = {};

            vertices.forEach(function(v) {

                var className = self.vertexIdToClassName(v.id),
                    classes = [className],
                    vertexState = self.stateForVertex(v);

                if ( vertexState.inGraph ) classes.push('graph-displayed');
                if ( vertexState.inMap ) classes.push('map-displayed');

                if (!v.imageSrcIsFromConcept) {
                    classes.push('has_preview');
                }

                classNamesForVertex[v.id] = classes.join(' ');
            });

            return classNamesForVertex;
        };

        this.after('initialize', function() {
            var self = this;

            this.classNameIndex = 0;
            this.classNameLookup = {};

            this.$node
                .addClass('vertex-list')
                .html(template({
                    vertices:this.attr.vertices,
                    infiniteScrolling: this.attr.infiniteScrolling && this.attr.total !== this.attr.vertices.length,
                    classNamesForVertex: this.classNameMapForVertices(this.attr.vertices)
                }));

            this.attachEvents();

            this.loadVisibleResultPreviews = _.debounce(this.loadVisibleResultPreviews.bind(this), 1000);
            this.loadVisibleResultPreviews();

            this.triggerInfiniteScrollRequest = _.debounce(this.triggerInfiniteScrollRequest.bind(this), 1000);
            this.triggerInfiniteScrollRequest();

            this.setupDraggables();

            this.onObjectsSelected(null, { edges:[], vertices:appData.selectedVertices});

            this.on('selectAll', this.onSelectAll);
            this.on('down', this.move);
            this.on('up', this.move);

            _.defer(function() {
                this.$node.scrollTop(0);
            }.bind(this))

        });

        this.move = function(e, data) {
            var previousSelected = this.$node.find('.active')[e.type === 'up' ? 'first' : 'last'](),
                moveTo = previousSelected[e.type === 'up' ? 'prev' : 'next']('.vertex-item');

            if (moveTo.length) {

                var selected = [];

                if (data.shiftKey) {
                    selected = selected.concat(appData.selectedVertices);
                    selected.push(appData.vertex(moveTo.data('vertexId')));
                } else {
                    selected.push(appData.vertex(moveTo.data('vertexId')));
                }

                this.trigger(document, 'defocusVertices');
                this.trigger('selectObjects', { vertices:selected });
            }
        };

        this.onSelectAll = function(e) {
            e.stopPropagation();

            var items = this.$node.find('.vertex-item').addClass('active');
            this.selectItems(items);
        };

        this.after('teardown', function() {
            this.$node.off('mouseenter mouseleave');
            this.scrollNode.off('scroll.vertexList');
            this.$node.empty();
        });

        this.attachEvents = function() {
            this.scrollNode = this.$node;
            while (this.scrollNode.length && this.scrollNode.css('overflow') !== 'auto') {
                this.scrollNode = this.scrollNode.parent();
            }
            this.scrollNode.on('scroll.vertexList', this.onResultsScroll.bind(this));

            this.$node.on('mouseenter mouseleave', '.vertex-item', this.onHoverItem.bind(this));

            this.on(document, 'verticesAdded', this.onVerticesUpdated);
            this.on(document, 'verticesUpdated', this.onVerticesUpdated);
            this.on(document, 'verticesDeleted', this.onVerticesDeleted);
            this.on(document, 'objectsSelected', this.onObjectsSelected);
            this.on(document, 'switchWorkspace', this.onWorkspaceClear);
            this.on(document, 'workspaceDeleted', this.onWorkspaceClear);
            this.on(document, 'workspaceLoaded', this.onWorkspaceLoaded);
            this.on('addInfiniteVertices', this.onAddInfiniteVertices);
        };

        this.setupDraggables = function() {
            this.applyDraggable(this.$node.find('a'));
            this.$node.droppable({ accept:'*', tolerance:'pointer' });
        };

        this.onHoverItem = function(evt) {
            if (this.disableHover === 'defocused') {
                return;
            } else if (this.disableHover) {
                this.disableHover = 'defocused';
                return this.trigger(document, 'defocusVertices');
            }

            var id = $(evt.target).closest('.vertex-item').data('vertexId');
            if (evt.type == 'mouseenter' && id) {
                this.trigger(document, 'focusVertices', { vertexIds:[id] });
            } else {
                this.trigger(document, 'defocusVertices');
            }
        };

        this.onResultsScroll = function(e) {
            if (!this.disableHover) {
                this.disableHover = true;
            }

            this.loadVisibleResultPreviews();

            if (this.attr.infiniteScrolling) {
                this.triggerInfiniteScrollRequest();
            }
        };

        this.triggerInfiniteScrollRequest = function() {
            if (!this.attr.infiniteScrolling) return;

            var loadingListElement = this.$node.find('.infinite-loading');

            if (this.scrollNode.length) {
                loadingListElement = loadingListElement.withinScrollable(this.scrollNode);
            }

            if (loadingListElement.length) {
                var data = { conceptType: this.attr.verticesConceptId };
                if (!this.offset) this.offset = this.attr.vertices.length;
                data.paging = {
                    offset: this.offset
                };
                this.trigger('infiniteScrollRequest', data);
            }
        };

        this.onAddInfiniteVertices = function(evt, data) {
            var loading = this.$node.find('.infinite-loading');

            if (!data.success) {
                loading.html(alertTemplate({ error: 'Error loading search results' }));
                this.attr.infiniteScrolling = false;
            } else if (data.vertices.length === 0) {
                loading.remove();
                this.attr.infiniteScrolling = false;
            } else {
                this.offset += data.vertices.length;
                var clsMap = this.classNameMapForVertices(data.vertices),
                    added = data.vertices.map(function(vertex) {
                        return vertexTemplate({
                            vertex:vertex,
                            classNamesForVertex: clsMap
                        });
                    });

                var lastItem = loading.prev();

                loading.before(added);

                var total = data.total || this.attr.total || 0;
                if (total === this.$node.find('.vertex-item').length) {
                    loading.remove();
                    this.attr.infiniteScrolling = false;
                } else {
                    this.triggerInfiniteScrollRequest();
                }

                this.loadVisibleResultPreviews();

                this.applyDraggable(this.$node.find('a'));
            }
        };

        this.loadVisibleResultPreviews = function() {
            var self = this;

            this.disableHover = false;

            var lisVisible = this.$node.find('.nav-list').children('li');
            if (this.scrollNode.length) {
                lisVisible = lisVisible.withinScrollable(this.scrollNode);
            }

            lisVisible.each(function() {
                var li = $(this),
                    vertex = appData.vertex(li.data('vertexId'));

                if (vertex && !vertex.imageSrcIsFromConcept && !li.data('previewLoaded')) {

                    var preview = li.data('previewLoaded', true)
                                    .find('.preview');

                    if (vertex.imageFramesSrc) {
                        VideoScrubber.attachTo(preview, {
                            posterFrameUrl: vertex.imageSrc,
                            videoPreviewImageUrl: vertex.imageFramesSrc
                        });
                    } else {
                        preview.html("<img src='" + vertex.imageSrc + "' />");
                    }
                }
            });
        };

        this.applyDraggable = function(el) {
            var self = this;

            el.draggable({
                helper:'clone',
                appendTo: 'body',
                revert: 'invalid',
                revertDuration: 250,
                scroll: false,
                zIndex: 100,
                distance: 10,
                multi: true,
                otherDraggablesClass: 'vertex-dragging',
                start: function(ev, ui) {
                    $(ui.helper).addClass('vertex-dragging');
                },
                selection: function(ev, ui) {
                    self.selectItems(ui.selected);
                }
            });
        };

        this.selectItems = function(items) {
            var vertices = appData.vertices(items.map(function() {
                    return $(this).data('vertexId');
                }).toArray());

            if (vertices.length > 1) {
                vertices.forEach (function (vertex) {
                    vertex.workspace = {
                        selected: true
                    };
                });
            }
            this.trigger(document, 'defocusVertices');
            this.trigger('selectObjects', { vertices:vertices });
        };

        this.onWorkspaceLoaded = function(evt, workspace) {
            this.onVerticesUpdated(evt, workspace.data || {});
        };

        // Track changes to vertices so we display the "Displayed in Graph" icon
        // in search results
        this.toggleItemIcons = function(id, data) {
            this.$node
                .find('li.' + this.vertexIdToClassName(id))
                .toggleClass('graph-displayed', data.inGraph)
                .toggleClass('map-displayed', data.inMap);
        };

        // Switching workspaces should clear the icon state and vertices
        this.onWorkspaceClear = function() {
            this.$node.find('li.graph-displayed').removeClass('graph-displayed');
            this.$node.find('li.map-displayed').removeClass('map-displayed');
        };

        this.onVerticesUpdated = function(event, data) {
            var self = this;
            (data.vertices || []).forEach(function(vertex) {
                self.toggleItemIcons(vertex.id, self.stateForVertex(vertex));
                var currentAnchor = self.$node.find('li.' + self.vertexIdToClassName(vertex.id)).children('a'),
                    newAnchor = $(vertexTemplate({
                        vertex: vertex,
                        classNamesForVertex: self.classNameMapForVertices([vertex]),
                    })).children('a'),
                    currentHtml = currentAnchor.html(),
                    src = vertex.imageSrc,
                    showImageSrc = src && !vertex.imageSrcIsFromConcept;

                if (currentAnchor.length) {
                    if (showImageSrc) {
                        $('<img/>').attr('src', src).appendTo(newAnchor.find('.preview'));
                    }

                    var newHtml = newAnchor.html();
                    if (currentAnchor.length && newHtml !== currentHtml) {
                        currentAnchor.html(newHtml).closest('.vertex-item').toggleClass('has_preview', showImageSrc);
                    }
                }
            });
        };

        this.onVerticesDeleted = function(event, data) {
            var self = this;
            (data.vertices || []).forEach(function(vertex) {
                self.toggleItemIcons(vertex.id, { inGraph:false, inMap:false });
            });
        };

        this.onObjectsSelected = function(event, data) {
            this.$node.find('.active').removeClass('active');

            var self = this,
                ids = _.chain(data.vertices)
                    .map(function(v) {
                        return '.' + self.vertexIdToClassName(v.id);
                    })
                    .value().join(',');

            $(ids, this.node).addClass('active');
        };
    }
});
<|endoftext|>


<commit_before>import { React, PropTypes, observer, styled, moment } from 'vendor';
import { Modal, Button, OverlayTrigger, Tooltip } from 'react-bootstrap';
import DateTimeInput from '../../components/date-time-input';
import { Formik, Form } from 'formik';
import { colors, fonts } from 'theme';
import Time from '../../helpers/time';
import ExtensionIcon, { GreenCircle, EIcon  } from '../../components/icons/extension';
import CheckBoxInput from '../../components/checkbox-input';
import SearchInput from '../../components/search-input';

const StudentExtensionInfo = observer(({ ux, student }) => {
  if (!student.extension) { return null; }

  return (
    <ExtensionIcon
      extension={student.extension}
      timezone={ux.course.timezone}
      inline={true}
    />
  );
});

const StudentWrapper = styled.div`
  ${GreenCircle} {
    margin-left: 0.5rem;
    margin-top: -1px;
  }
  width: 210px;
  margin: 10px 25px 9px 0;
  display: flex;
  align-items: center;

  label {
    max-width: 170px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
`;

const DateTimes = styled.div`
  display: flex;
  > * { flex: 1; }
  .date-time-input + .date-time-input {
    margin-left: 1rem;
  }
  .date-time-input {
    display: flex;
    align-items: center;
    label {
      margin-right: 24px;
      margin-bottom: 0;
    }
  }
`;

const StudentsList = styled.div`
  margin: 10px 0 36px;
  display: flex;
  flex-wrap: wrap;

  label {
    margin-left: 17px;
    margin-bottom: 0;
  }
`;

const LegendBar = styled.div`
  margin: 2.5rem 2rem 2rem 0;
  display: flex;
  align-items: baseline;
`;

const ExtensionText = styled.div`
  ${fonts.faces.light};
  font-size: 1.4rem;
  line-height: 2.2rem;
  margin-left: 1rem;
  color: ${colors.neutral.lite};
  max-width: 525px;
`;

const SelectTitle = styled.div`
  font-weight: bold;
  margin-bottom: 1rem;
`;

const Toolbar = styled.div`
  margin: 0 0 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  input { height: 100%; }
  .input-group { width: 255px; }
`;

const StyledModal = styled(Modal)`
  .modal-dialog {
    min-width: 800px;
  }

  .modal-footer {
    padding: 0 40px 40px;
  }

  .modal-body {
    padding: 22px 40px;
  }

  .modal-header {
    padding: 25px 40px;
    font-weight: bold;
    font-size: 1.8rem;

    .close {
      font-size: 3rem;
      margin-top: -1.8rem;
    }
  }

  .extension-icon {
    width: 1.5rem;
    height: 1.5rem;
    line-height: 1.5rem;
  }
`;

const SelectAll = styled.div`
  ${props => props.hide && 'visibility: hidden;'}

  label {
    margin-left: 17px;
    margin-bottom: 0;
  }
`;

const ExtendModal = observer(({ ux, form: { isValid, values } }) => {
  return (
    <StyledModal
      show={ux.isDisplayingGrantExtension}
      backdrop="static"
      onHide={ux.cancelDisplayingGrantExtension}
    >
      <Form>
        <Modal.Header closeButton={true}>
          Grant extension for {ux.selectedPeriod.name}
        </Modal.Header>
        <Modal.Body>
          <Toolbar>
            <SelectTitle>Select student(s):</SelectTitle>
            <SearchInput onChange={ux.onSearchExtensionStudentChange} />
          </Toolbar>
          <SelectAll hide={ux.hideToggleGrantExtensionAllStudents}>
            <CheckBoxInput
              onChange={ux.toggleGrantExtensionAllStudents}
              label="Select all"
              labelSize="lg"
              standalone={true}
              checked={ux.allExtensionStudentsSelected}
            />
          </SelectAll>
          <StudentsList>
            {ux.extensionStudents.map(student => <Student key={student.role_id} ux={ux} student={student} />)}
          </StudentsList>
          <DateTimes>
            <DateTimeInput
              label="New due date:"
              name="extension_due_date"
              disabledDate={ux.course.isInvalidAssignmentDate}
              timezone={ux.course.timezone}
              validate={(d) => { // eslint-disable-line consistent-return
                if (d.isBefore(Time.now)) return 'Due date cannot be set in the past';
                if (d.isAfter(values.extension_close_date)) return 'Due date cannot be after Close date';
              }}
            />
            <DateTimeInput
              label="New close date:"
              name="extension_close_date"
              timezone={ux.course.timezone}
              disabledDate={ux.course.isInvalidAssignmentDate}
              validate={d => d.isBefore(values.extension_due_date) && 'Close date cannot be before Due date'}
            />
          </DateTimes>
          <LegendBar>
            <EIcon />
            <ExtensionText>
              Students who’ve been granted an extension are denoted with a green circle with E.
              Hover over the icon to see the new due date for that student.
            </ExtensionText>
          </LegendBar>
        </Modal.Body>
        <Modal.Footer>
          <Button
            size="lg"
            variant="default"
            className="btn-standard"
            onClick={ux.cancelDisplayingGrantExtension}
          >Cancel</Button>
          <Button
            size="lg"
            variant="primary"
            className="btn-standard"
            type="submit"
            disabled={isValid == false || (!ux.isPendingExtensions)}
          >Save</Button>
        </Modal.Footer>
      </Form>
    </StyledModal>
  );
});

const Student = observer(({ student, ux }) => {
  const checked = !!ux.pendingExtensions.get(student.role_id.toString(10));
  return (
    <StudentWrapper>
      <CheckBoxInput
        onChange={({ target: { checked } }) => ux.pendingExtensions.set(student.role_id.toString(10), checked)}
        checked={checked}
        standalone={true}
        label={`${student.first_name} ${student.last_name}`}
        labelSize="lg"
      />
      <StudentExtensionInfo ux={ux} student={student} />
    </StudentWrapper>
  );
});

const GrantExtension = observer(({ ux }) => {
  if (!ux.taskPlan.canGrantExtension) {
    return null;
  }

  return (
    <>
      <OverlayTrigger
        placement="bottom"
        overlay={<Tooltip>Select and grant time extension to student(s)</Tooltip>}
      >
        <Button
          variant="light"
          className="btn-standard"
          onClick={() => ux.isDisplayingGrantExtension=true}
        >
          Grant Extension
        </Button>
      </OverlayTrigger>
      {ux.isDisplayingGrantExtension && (
        <Formik
          onSubmit={ux.saveDisplayingGrantExtension}
          initialValues={{
            extension_due_date: moment.tz(ux.course.timezone).add(1, 'day'),
            extension_close_date: moment.tz(ux.course.timezone).add(1, 'week'),
          }}
        >
          {(form) => <ExtendModal ux={ux} form={form} />}
        </Formik>)}
    </>
  );
});

GrantExtension.propTypes = {
  ux: PropTypes.object.isRequired,
};

export default GrantExtension;
<commit_msg>split students into different columns based on count<commit_after>import { React, PropTypes, observer, styled, moment } from 'vendor';
import { Modal, Button, OverlayTrigger, Tooltip } from 'react-bootstrap';
import DateTimeInput from '../../components/date-time-input';
import { Formik, Form } from 'formik';
import { colors, fonts } from 'theme';
import Time from '../../helpers/time';
import ExtensionIcon, { GreenCircle, EIcon  } from '../../components/icons/extension';
import CheckBoxInput from '../../components/checkbox-input';
import SearchInput from '../../components/search-input';

const StudentExtensionInfo = observer(({ ux, student }) => {
  if (!student.extension) { return null; }

  return (
    <ExtensionIcon
      extension={student.extension}
      timezone={ux.course.timezone}
      inline={true}
    />
  );
});

const StudentWrapper = styled.div`
  ${GreenCircle} {
    margin-left: 0.8rem;
    margin-top: -1px;
  }
  width: 100%;
  margin: 10px 25px 9px 0;
  display: flex;
  align-items: center;

  &:first-child:nth-last-child(n + 11),
  &:first-child:nth-last-child(n + 11) ~ & {
    width: 335px;
  }

  &:first-child:nth-last-child(n + 21),
  &:first-child:nth-last-child(n + 21) ~ & {
    width: 215px;
  }

  label {
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
`;

const DateTimes = styled.div`
  display: flex;
  > * { flex: 1; }
  .date-time-input + .date-time-input {
    margin-left: 1rem;
  }
  .date-time-input {
    display: flex;
    align-items: center;
    label {
      margin-right: 24px;
      margin-bottom: 0;
    }
  }
`;

const StudentsList = styled.div`
  margin: 10px 0 36px;
  display: flex;
  flex-wrap: wrap;

  label {
    margin-left: 17px;
    margin-bottom: 0;
  }
`;

const LegendBar = styled.div`
  margin: 2.5rem 2rem 2rem 0;
  display: flex;
  align-items: baseline;
`;

const ExtensionText = styled.div`
  ${fonts.faces.light};
  font-size: 1.4rem;
  line-height: 2.2rem;
  margin-left: 1rem;
  color: ${colors.neutral.lite};
  max-width: 525px;
`;

const SelectTitle = styled.div`
  font-weight: bold;
  margin-bottom: 1rem;
`;

const Toolbar = styled.div`
  margin: 0 0 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  input { height: 100%; }
  .input-group { width: 255px; }
`;

const StyledModal = styled(Modal)`
  .modal-dialog {
    min-width: 800px;
  }

  .modal-footer {
    padding: 0 40px 40px;
  }

  .modal-body {
    padding: 22px 40px;
  }

  .modal-header {
    padding: 25px 40px;
    font-weight: bold;
    font-size: 1.8rem;

    .close {
      font-size: 3rem;
      margin-top: -1.8rem;
    }
  }

  .extension-icon {
    width: 1.5rem;
    height: 1.5rem;
    line-height: 1.5rem;
  }
`;

const SelectAll = styled.div`
  ${props => props.hide && 'visibility: hidden;'}

  label {
    margin-left: 17px;
    margin-bottom: 0;
  }
`;

const ExtendModal = observer(({ ux, form: { isValid, values } }) => {
  return (
    <StyledModal
      show={ux.isDisplayingGrantExtension}
      backdrop="static"
      onHide={ux.cancelDisplayingGrantExtension}
    >
      <Form>
        <Modal.Header closeButton={true}>
          Grant extension for {ux.selectedPeriod.name}
        </Modal.Header>
        <Modal.Body>
          <Toolbar>
            <SelectTitle>Select student(s):</SelectTitle>
            <SearchInput onChange={ux.onSearchExtensionStudentChange} />
          </Toolbar>
          <SelectAll hide={ux.hideToggleGrantExtensionAllStudents}>
            <CheckBoxInput
              onChange={ux.toggleGrantExtensionAllStudents}
              label="Select all"
              labelSize="lg"
              standalone={true}
              checked={ux.allExtensionStudentsSelected}
            />
          </SelectAll>
          <StudentsList>
            {ux.extensionStudents.map(student => <Student key={student.role_id} ux={ux} student={student} />)}
          </StudentsList>
          <DateTimes>
            <DateTimeInput
              label="New due date:"
              name="extension_due_date"
              disabledDate={ux.course.isInvalidAssignmentDate}
              timezone={ux.course.timezone}
              validate={(d) => { // eslint-disable-line consistent-return
                if (d.isBefore(Time.now)) return 'Due date cannot be set in the past';
                if (d.isAfter(values.extension_close_date)) return 'Due date cannot be after Close date';
              }}
            />
            <DateTimeInput
              label="New close date:"
              name="extension_close_date"
              timezone={ux.course.timezone}
              disabledDate={ux.course.isInvalidAssignmentDate}
              validate={d => d.isBefore(values.extension_due_date) && 'Close date cannot be before Due date'}
            />
          </DateTimes>
          <LegendBar>
            <EIcon />
            <ExtensionText>
              Students who’ve been granted an extension are denoted with a green circle with E.
              Hover over the icon to see the new due date for that student.
            </ExtensionText>
          </LegendBar>
        </Modal.Body>
        <Modal.Footer>
          <Button
            size="lg"
            variant="default"
            className="btn-standard"
            onClick={ux.cancelDisplayingGrantExtension}
          >Cancel</Button>
          <Button
            size="lg"
            variant="primary"
            className="btn-standard"
            type="submit"
            disabled={isValid == false || (!ux.isPendingExtensions)}
          >Save</Button>
        </Modal.Footer>
      </Form>
    </StyledModal>
  );
});

const Student = observer(({ student, ux }) => {
  const checked = !!ux.pendingExtensions.get(student.role_id.toString(10));
  return (
    <StudentWrapper>
      <CheckBoxInput
        onChange={({ target: { checked } }) => ux.pendingExtensions.set(student.role_id.toString(10), checked)}
        checked={checked}
        standalone={true}
        label={`${student.first_name} ${student.last_name}`}
        labelSize="lg"
      />
      <StudentExtensionInfo ux={ux} student={student} />
    </StudentWrapper>
  );
});

const GrantExtension = observer(({ ux }) => {
  if (!ux.taskPlan.canGrantExtension) {
    return null;
  }

  return (
    <>
      <OverlayTrigger
        placement="bottom"
        overlay={<Tooltip>Select and grant time extension to student(s)</Tooltip>}
      >
        <Button
          variant="light"
          className="btn-standard"
          onClick={() => ux.isDisplayingGrantExtension=true}
        >
          Grant Extension
        </Button>
      </OverlayTrigger>
      {ux.isDisplayingGrantExtension && (
        <Formik
          onSubmit={ux.saveDisplayingGrantExtension}
          initialValues={{
            extension_due_date: moment.tz(ux.course.timezone).add(1, 'day'),
            extension_close_date: moment.tz(ux.course.timezone).add(1, 'week'),
          }}
        >
          {(form) => <ExtendModal ux={ux} form={form} />}
        </Formik>)}
    </>
  );
});

GrantExtension.propTypes = {
  ux: PropTypes.object.isRequired,
};

export default GrantExtension;
<|endoftext|>


<commit_before>var graph = {};

(function() {
    var canvas;
    var ctx;
    var width;
    var height;

    var bgColor = '#eee';
    var barColor = '#999';
    var highlightColor = '#69f';

    graph.init = function(c) {
        canvas = c;
        ctx = canvas.getContext('2d');
        width = canvas.offsetWidth;
        height = canvas.offsetHeight;
    };

    graph.draw = function(highlightIndexes, values) {
        ctx.fillStyle = bgColor;
        ctx.fillRect(0, 0, width, height);
        var idx1 = highlightIndexes[0];
        var idx2 = highlightIndexes[1];

        var size = values.length;
        var barWidth = (width - size + 1) / size;
        var barHeightUnit = height / size;

        var x = 0;
        var h = 0;
        ctx.fillStyle = barColor;
        for(var i = 0; i < values.length; i++) {
            h = values[i] * barHeightUnit;
            if(i === idx1 || i === idx2) {
                ctx.fillStyle = highlightColor;
                ctx.fillRect(x, height- h, barWidth, h);
                ctx.fillStyle = barColor;
            } else {
                ctx.fillRect(x, height- h, barWidth, h);
            }
            x = x + barWidth + 1;
        }
    };
})();
<commit_msg>グラフの色を変更した<commit_after>var graph = {};

(function() {
    var canvas;
    var ctx;
    var width;
    var height;

    var bgColor = '#333';
    var barColor = '#6cf';
    var highlightColor = '#cf6';

    graph.init = function(c) {
        canvas = c;
        ctx = canvas.getContext('2d');
        width = canvas.offsetWidth;
        height = canvas.offsetHeight;
    };

    graph.draw = function(highlightIndexes, values) {
        ctx.fillStyle = bgColor;
        ctx.fillRect(0, 0, width, height);
        var idx1 = highlightIndexes[0];
        var idx2 = highlightIndexes[1];

        var size = values.length;
        var barWidth = (width - size + 1) / size;
        var barHeightUnit = height / size;

        var x = 0;
        var h = 0;
        ctx.fillStyle = barColor;
        for(var i = 0; i < values.length; i++) {
            h = values[i] * barHeightUnit;
            if(i === idx1 || i === idx2) {
                ctx.fillStyle = highlightColor;
                ctx.fillRect(x, height- h, barWidth, h);
                ctx.fillStyle = barColor;
            } else {
                ctx.fillRect(x, height- h, barWidth, h);
            }
            x = x + barWidth + 1;
        }
    };
})();
<|endoftext|>


<commit_before>const printAttrs = attrs =>
  Object.keys(attrs)
    .map(key => `${key}="${attrs[key]}"`)
    .join(' ');

const printNode = node => {
  const {nodeName, ...attrs} = node;
  return `<${nodeName} ${printAttrs(attrs)}/>`;
};

export default function print({filterEffects, filterAttrs}){
  const children = filterEffects.map(printNode)
    .join('\n');

  return `<filter ${printAttrs(filterAttrs)}>\n${children}\n</filter>`;
}
<commit_msg>Update print function to handle children properly<commit_after>const printAttrs = attrs =>
  Object.keys(attrs)
    .map(key => `${key}="${attrs[key]}"`)
    .join(' ');

const printNode = ({nodeName, children, ...attrs}) =>
  children
    ? `<${nodeName} ${printAttrs(attrs)}>
  ${children.map(printNode).join('\n')}
</${nodeName}>`
    : `<${nodeName} ${printAttrs(attrs)}/>`;

export default function print({filterEffects, filterAttrs}){
  const children = filterEffects.map(printNode)
    .join('\n');

  return `<filter ${printAttrs(filterAttrs)}>\n${children}\n</filter>`;
}
<|endoftext|>


<commit_before>// Originally found in Fantasia.

// Description:
// Display Combat Power (CP) information when holding down ALT in numerical value as opposed to just ranking. (example: weakest White Spider > 50 weakest White Spider)

// Walkthrough:
// IDA unicode string view -> <mini>WEAKEST </mini> (there are others for
// strong, awful, etc)
// check the xrefs (should only be one)
// These are the strings we replace and this is the function we intercept.
//
// The basic idea is to fix the checks this funciton does so that it runs its
// switch statement on other players, then patch the switch statement so that
// it only uses the string our hook generates for each player. We end up using
// the same functions this function uses to determine the combat power
// difference, and end up reimplementing the switch statement ourselves in a
// way.

//
// Functions we need to call (that are called by the function we found).
//
var calcCombatPowerDifferencePtr = scan('55 8B EC 83 EC 08 56 8B F1 8B 06 8B 50 58 FF D2 8B C8 E8 ? ? ? ? D8 1D');
var calcCombatPowerDifference = new NativeFunction(calcCombatPowerDifferencePtr, 'int', ['pointer', 'int'], 'thiscall');

var getUnkObjectCall = scan('E8 ? ? ? ? 8B F8 3B FB 0F 84 ? ? ? ? 8B CE E8 ? ? ? ? 84 C0 0F 84 ? ? ? ? 8B CE');
var getUnkObjectPtr = calcAbsAddress(getUnkObjectCall);
var getUnkObject = new NativeFunction(getUnkObjectPtr, 'pointer', ['pointer'], 'thiscall');

//
// Checks we need to fix.
//
var isPlayerCheck1 = scan('0F 85 ?? ?? ?? ?? 57 8B CE E8 ?? ?? ?? ?? 84 C0 75 76'); // This is from the function that calls the one we found.
var isPlayerCheck2 = scan('0F 84 ?? ?? ?? ?? 8B CE E8 ?? ?? ?? ?? 84 C0 0F 85 ?? ?? ?? ?? 8B CE E8 ?? ?? ?? ?? 84 C0 0F 85 ?? ?? ?? ?? 8B 06'); // This is in the function we found near the top.
var isLocalPlayerCheck = scan('0F 85 ?? ?? ?? ?? 89 5D F0 C7 45 ?? ?? ?? ?? ?? 89 5D E8'); // this is in the function we found right before the switch.

//
// This is the main switch statement in the function we found.
//
// It looks like this:
// cmp eax, 5
// ja <addr>
// jmp off_<addr>[eax * 4]
//
var theSwitch = scan(' 83 F8 05 77 30');

//
// Make room for our replacement string.
//
var strMem = allocateMemory(1000);

//
// Apply all our patches.
///

// The checks.
patch(isPlayerCheck1, [0x90, 0xE9]);
patch(isPlayerCheck2, Array(6).fill(0x90));
patch(isLocalPlayerCheck, Array(6).fill(0x90));

// Patches the common cmp, ja and jmp instructions used by switch statements.
patch(theSwitch, Array(12).fill(0x90));

// Overwrite the location of the default string (just the <mini>WEAKEST </mini> one) with our new string.
patchPointer(theSwitch.add(12 + 1), strMem);

//
// Hook the function we found.
//
var createAltNameStr = scan('55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 18 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 ?? ?? ?? ?? 8B F1 33 DB 89 5D E0');

Interceptor.attach(createAltNameStr, {
    onEnter(args) {
        var getUnkObjectThisPtr = Memory.readPointer(getUnkObjectCall.sub(4));

        if (getUnkObjectThisPtr.isNull()) {
            return;
        }

        var getUnkObjectThis = Memory.readPointer(getUnkObjectThisPtr);

        if (getUnkObjectThis.isNull()) {
            return;
        }

        var v3 = getUnkObject(getUnkObjectThis);

        if (v3.isNull()) {
            return;
        }

        // Follow through the execution of the function to find these offsets,
        // they are pretty easy to get.
        var thisPtr = this.context.ecx;
        var entPtr = Memory.readPointer(thisPtr.add(0x90));
        var cp = Memory.readFloat(entPtr.add(0x11c));

        var cpStr = cp.toFixed();

        switch (calcCombatPowerDifference(v3, thisPtr.toInt32()))
        {
        case 1: cpStr += " <mini>WEAKEST </mini>"; break;
        case 2: cpStr += " <mini>WEAK </mini>"; break;
        case 4: cpStr += " <mini>STRONG </mini>"; break;
        case 5: cpStr += " <mini>AWFUL </mini>"; break;
        case 6: cpStr += " <mini>BOSS </mini>"; break;
        default: cpStr += " <mini>SIMILAR </mini>";
        }

        Memory.writeUtf16String(strMem, cpStr);
    }
});

<commit_msg>Fix ShowCombatPower.js<commit_after>// Originally found in Fantasia.

// Description:
// Display Combat Power (CP) information when holding down ALT in numerical value as opposed to just ranking. (example: weakest White Spider > 50 weakest White Spider)

// Walkthrough:
// IDA unicode string view -> <mini>WEAKEST </mini> (there are others for
// strong, awful, etc)
// check the xrefs (should only be one)
// These are the strings we replace and this is the function we intercept.
//
// The basic idea is to fix the checks this funciton does so that it runs its
// switch statement on other players, then patch the switch statement so that
// it only uses the string our hook generates for each player. We end up using
// the same functions this function uses to determine the combat power
// difference, and end up reimplementing the switch statement ourselves in a
// way.

//
// Functions we need to call (that are called by the function we found).
//
var calcCombatPowerDifferencePtr = scan('55 8B EC 83 EC 08 56 8B F1 8B 06 8B 50 58 FF D2 8B C8 E8 ? ? ? ? D8 1D');
var calcCombatPowerDifference = new NativeFunction(calcCombatPowerDifferencePtr, 'int', ['pointer', 'int'], 'thiscall');

var getUnkObjectCall = scan('E8 ? ? ? ? 8B F8 3B FB 0F 84 ? ? ? ? 8B CE E8 ? ? ? ? 84 C0 0F 84 ? ? ? ? 8B CE');
var getUnkObjectPtr = calcAbsAddress(getUnkObjectCall);
var getUnkObject = new NativeFunction(getUnkObjectPtr, 'pointer', ['pointer'], 'thiscall');

//
// Checks we need to fix.
//
var isPlayerCheck1 = scan('0F 85 ? ? ? ? 56 8B CF E8 ? ? ? ? 84 C0 0F 85 ? ? ? ? 8B CE E8 ? ? ? ? C1 EA 10'); // This is from the function that calls the one we found.
var isPlayerCheck2 = scan('0F 84 ?? ?? ?? ?? 8B CE E8 ?? ?? ?? ?? 84 C0 0F 85 ?? ?? ?? ?? 8B CE E8 ?? ?? ?? ?? 84 C0 0F 85 ?? ?? ?? ?? 8B 06'); // This is in the function we found near the top.
var isLocalPlayerCheck = scan('0F 85 ?? ?? ?? ?? 89 5D F0 C7 45 ?? ?? ?? ?? ?? 89 5D E8'); // this is in the function we found right before the switch.

//
// This is the main switch statement in the function we found.
//
// It looks like this:
// cmp eax, 5
// ja <addr>
// jmp off_<addr>[eax * 4]
//
var theSwitch = scan(' 83 F8 05 77 30');

//
// Make room for our replacement string.
//
var strMem = allocateMemory(1000);

//
// Apply all our patches.
///

// The checks.
patch(isPlayerCheck1, [0x90, 0xE9]);
patch(isPlayerCheck2, Array(6).fill(0x90));
patch(isLocalPlayerCheck, Array(6).fill(0x90));

// Patches the common cmp, ja and jmp instructions used by switch statements.
patch(theSwitch, Array(12).fill(0x90));

// Overwrite the location of the default string (just the <mini>WEAKEST </mini> one) with our new string.
patchPointer(theSwitch.add(12 + 1), strMem);

//
// Hook the function we found.
//
var createAltNameStr = scan('55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 18 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 ?? ?? ?? ?? 8B F1 33 DB 89 5D E0');

Interceptor.attach(createAltNameStr, {
    onEnter(args) {
        var getUnkObjectThisPtr = Memory.readPointer(getUnkObjectCall.sub(4));

        if (getUnkObjectThisPtr.isNull()) {
            return;
        }

        var getUnkObjectThis = Memory.readPointer(getUnkObjectThisPtr);

        if (getUnkObjectThis.isNull()) {
            return;
        }

        var v3 = getUnkObject(getUnkObjectThis);

        if (v3.isNull()) {
            return;
        }

        // Follow through the execution of the function to find these offsets,
        // they are pretty easy to get.
        var thisPtr = this.context.ecx;
        var entPtr = Memory.readPointer(thisPtr.add(0x90));
        var cp = Memory.readFloat(entPtr.add(0x11c));

        var cpStr = cp.toFixed();

        switch (calcCombatPowerDifference(v3, thisPtr.toInt32()))
        {
        case 1: cpStr += " <mini>WEAKEST </mini>"; break;
        case 2: cpStr += " <mini>WEAK </mini>"; break;
        case 4: cpStr += " <mini>STRONG </mini>"; break;
        case 5: cpStr += " <mini>AWFUL </mini>"; break;
        case 6: cpStr += " <mini>BOSS </mini>"; break;
        default: cpStr += " <mini>SIMILAR </mini>";
        }

        Memory.writeUtf16String(strMem, cpStr);
    }
});

<|endoftext|>