<commit_before>var loadJSON   = require('pex-io/loadJSON');
var loadBinary = require('pex-io/loadBinary');
var loadText   = require('pex-io/loadText');
var loadImage  = require('pex-io/loadImage');
var log        = require('debug')('pex/gltf');
var info       = require('debug')('pex/gltf/info');
var path       = require('path');
var async      = require('async');
var iterateObject = require("iterate-object");

var WebGLConstants = {
    34963: 'ELEMENT_ARRAY_BUFFER',  //0x8893
    34962: 'ARRAY_BUFFER',          //0x8892
     5123: 'UNSIGNED_SHORT',        //0x1403
     5126: 'FLOAT',                 //0x1406
        4: 'TRIANGLES',             //0x0004
    35678: 'SAMPLER_2D',            //0x8B5E
    35664: 'FLOAT_VEC2',            //0x8B50
    35665: 'FLOAT_VEC3',            //0x8B51
    35666: 'FLOAT_VEC4',            //0x8B52
    35676: 'FLOAT_MAT4',            //0x8B5C
     5126: 'FLOAT'                  //0x1406
};

function handleBuffer(ctx, json, basePath, bufferName, bufferInfo, callback) {
    log('handleBuffer', bufferName, bufferInfo.uri);
    if (bufferInfo.uri) {
        loadBinary(basePath + '/' + bufferInfo.uri, function(err, data) {
            bufferInfo.arrayBuffer = data; //TODO: obj addon use _
            callback(err, data);
        });
    }
    else {
        throw new Error('gltf/handleBuffer missing uri in ' + JSON.stringify(bufferInfo));
    }
}

function handleBufferView(ctx, json, basePath, bufferViewName, bufferViewInfo, callback) {
   log('handleBufferView', bufferViewName);
   var buffer = json.buffers[bufferViewInfo.buffer];
   bufferViewInfo._typedArray = null;
   if (bufferViewInfo.target == 34963) { //ELEMENT_ARRAY_BUFFER //TODO: ctx constant
      //TODO: Slice or not to slice the buffer
      bufferViewInfo.buffer = buffer.arrayBuffer.slice(bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength); //ADDON
      info('slice', bufferViewName, bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength, '=', bufferViewInfo.buffer.byteLength);
      bufferViewInfo._buffer = bufferViewInfo.buffer;
      bufferViewInfo._typedArray = new Uint16Array(bufferViewInfo.buffer);
   }
   if (bufferViewInfo.target == 34962) { //ARRAY_BUFFER //TODO: ctx constant
      //TODO: Slice or not to slice the buffer
      bufferViewInfo.buffer = buffer.arrayBuffer.slice(bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength);
      info('slice', bufferViewName, bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength, '=', bufferViewInfo.buffer.byteLength);
      //bufferViewInfo.buffer = buffer.arrayBuffer;
      bufferViewInfo._buffer = bufferViewInfo.buffer;
      bufferViewInfo._typedArray = new Float32Array(bufferViewInfo.buffer);
   }
   log('handleBufferView', bufferViewName, WebGLConstants[bufferViewInfo.target], bufferViewInfo.byteOffset, '..', bufferViewInfo.byteLength, '/', buffer.arrayBuffer.byteLength)
   callback(null, bufferViewInfo);
}

function handleAccessor(ctx, json, basePath, accessorName, accessorInfo, callback) {
    log('handleAccessor', accessorName);
    callback(null, accessorInfo);
}

function linkPrimitive(json, primitiveName, primitiveInfo) {
    log('handlePrimitive', primitiveName)
    primitiveInfo.indices.accessor = json.accessors[primitiveInfo.indices];
    Object.keys(primitiveInfo.attributes).forEach(function(attribute) {
        primitiveInfo.attributes[attribute].accessor = json.accessors[primitiveInfo.attributes[attribute]];
    })
}

function handleMesh(ctx, json, basePath, meshName, meshInfo, callback) {
    log('handleMesh', meshInfo.name);
    meshInfo.primitives.forEach(function(primitiveInfo, primitiveIndex) {
        linkPrimitive(json, primitiveIndex, primitiveInfo);
    })
    callback(null, meshInfo);
}

function buildMeshes(ctx, json, callback) {
    log('buildMeshes');
    var AttributeLocationMap = {
        "POSITION": ctx.ATTRIB_POSITION,
        "NORMAL": ctx.ATTRIB_NORMAL,
        "TEXCOORD_0": ctx.ATTRIB_TEX_COORD_0
    }

    log('buildMesh AttributeNameMap');

    var AttributeSizeMap = {
        "SCALAR": 1,
        "VEC3": 3,
        "VEC2": 2
    }

    function buildBufferInfo(accessorName) {
        var accessorInfo = json.accessors[accessorName];
        var size = AttributeSizeMap[accessorInfo.type];
        //TODO: any other way to limit attrib count?
        var data = json.bufferViews[accessorInfo.bufferView]._typedArray;//.subarray(0, accessorInfo.count * size);
        var buffer = json.bufferViews[accessorInfo.bufferView]._buffer;
        if (buffer) {
            if (json.bufferViews[accessorInfo.bufferView].target == 34963) { //TODO: use ctx
                data = new Uint16Array(buffer.slice(accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 2));
            }
            else {
                data = new Float32Array(buffer.slice(accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 4));
            }
            info('subarray', accessorName, accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 4);
        }
        else {
            info('subarray', accessorName, accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 2);
        }

        var bufferInfo = {
            data: data,
            opts: {
                offset: 0,
                stride: accessorInfo.byteStride,
                size: size
            }
        };
        log(bufferInfo.opts);
        return bufferInfo;
    }

    iterateObject(json.meshes, function(meshInfo, meshName, meshIndex) {
        //if (meshIndex != 2) return; //FIXME: TEMP!
        log('buildMesh', meshName);
        meshInfo.primitives.forEach(function(primitiveInfo, primitiveIndex) {
            log('buildPrimitive', primitiveIndex);

            //var va = new VertexArray(ctx); //TODO: use ctx

            var attributes = [];

            iterateObject(primitiveInfo.attributes, function(accessorName, attributeSemantic) {
                var attributeInfo = buildBufferInfo(accessorName);
                var attributeLocation = AttributeLocationMap[attributeSemantic];
                log('buildAttribute', attributeSemantic, attributeInfo.opts);
                //va.addAttribute(attributeName, attributeInfo.data, attributeInfo.opts); //TODO: use ctx
                //
                var vertexBuffer = ctx.createBuffer(ctx.ARRAY_BUFFER, attributeInfo.data, ctx.STATIC_DRAW);
                attributes.push({
                    buffer: vertexBuffer,
                    location: attributeLocation,
                    size: attributeInfo.opts.size,
                    stride: attributeInfo.opts.stride,
                    offset: attributeInfo.opts.offset
                })
            });

            var indexBufferInfo = buildBufferInfo(primitiveInfo.indices);

            log('buildIndexBuffer', indexBufferInfo.opts, 'len:', indexBufferInfo.data.length);
            //va.addIndexBuffer(indexBufferInfo.data, indexBufferInfo.opts); //TODO: use ctx

            var indexBuffer = ctx.createBuffer(ctx.ELEMENT_ARRAY_BUFFER, indexBufferInfo.data, ctx.STATIC_DRAW);

            var va = ctx.createVertexArray(attributes, indexBuffer);

            primitiveInfo.vertexArray = va; //TODO: use ctx

            //* var vertexArray = new VertexArray(ctx,[
            //*     {buffer : buffer0, location : ctx.ATTRIB_POSITION, size : 3, stride : 0, offset : 0 },
            //*     {buffer : buffer0, location : ctx.ATTRIB_NORMAL, size : 3, stride : 0, offset : 4 * 3 * 4},
            //*     {buffer : buffer1, location : ctx.ATTRIB_COLOR, size : 4},
            //* ], indexBuffer);
        })//
    })

  callback(null, json);
}

function handleShader(ctx, json, basePath, shaderName, shaderInfo, callback) {
    log('handleShader', shaderName);
    if (shaderInfo.uri) {
        loadText(basePath + '/' + shaderInfo.uri, function(err, srcStr) {
            //TODO: add error handling
            log('handleShader');
            //precision is already added in Program class
            shaderInfo._src = srcStr.replace('precision highp float;', ''); //TODO: is it?
            callback(err, shaderInfo);
        });
    }
    else {
        throw new Error('gltf/handleShader missing uri in ' + JSON.stringify(shaderInfo));
    }
}

function handleImage(ctx, json, basePath, imageName, imageInfo, callback) {
    log('handleImage', imageInfo.uri);
    if (imageInfo.uri) {
        var url = basePath + '/' + imageInfo.uri;
        loadImage(url, function(err, img) {
            if (err) {
                log('handleImage', err.toString());
                callback(err, null);
            }
            else {
                imageInfo._img = img;
                callback(null, img);
            }
        })
    }
    else {
        throw new Error('gltf/handleImage missing uri in ' + JSON.stringify(imageInfo));
    }
}

function handleTexture(ctx, json, basePath, textureName, textureInfo, callback) {
    log('handleTexture', textureInfo.source)
    if (textureInfo.source) {
        var img = json.images[textureInfo.source]._img;
        var opts = {
            magFilter: ctx.NEAREST,
            minFilter: ctx.NEAREST
        }
        textureInfo._texture = ctx.createTexture2D(img, img.width, img.height, opts);
        callback(null, textureInfo);
    }
    else {
        throw new Error('gltf/handleTexture missing uri in ' + JSON.stringify(textureInfo));
    }
}

function handleProgram(ctx, json, basePath, programName, programInfo, callback) {
    log('handleProgram', programName);
    var vertSrc = json.shaders[programInfo.vertexShader]._src;
    var fragSrc = json.shaders[programInfo.fragmentShader]._src;
    //FIXME: hardcoded
    vertSrc = vertSrc.replace(/a_position/g, 'position');
    vertSrc = vertSrc.replace(/a_normal/g, 'normal');
    vertSrc = vertSrc.replace(/a_texcoord0/g, 'texcoord');
    programInfo._program = ctx.createProgram(vertSrc, fragSrc);
    callback(null, programInfo);
}

function handleNode(ctx, json, basePath, nodeName, nodeInfo, callback) {
    log('handleNode', nodeName);
    //FIXME: solve that with Ramda partial
    nodeInfo.children = nodeInfo.children.map(function(childNodeName) {
        json.nodes[childNodeName].parent = nodeInfo;
        return json.nodes[childNodeName];
    })
    callback(null, nodeInfo);
}

function handleAll(typeName, handler, ctx, json, basePath, callback) {
    log('handleAll', typeName);
    if (!json[typeName]) {
        log('missing', typeName);
        return callback(null, null);
    }
    async.map(
        Object.keys(json[typeName]),
        function(nodeName, callback) {
            handler(ctx, json, basePath, nodeName, json[typeName][nodeName], callback);
        },
        callback
    )
}

function load(ctx, file, callback) {
    var basePath = path.dirname(file);
    log('load ', file);
    loadJSON(file, function(err, json) {
        if (err) {
            return callback(err, null);
        }
        async.series([
            function(callback) { handleAll('buffers'    , handleBuffer    , ctx, json, basePath, callback); },
            function(callback) { handleAll('bufferViews', handleBufferView, ctx, json, basePath, callback); },
            function(callback) { handleAll('accessors'  , handleAccessor  , ctx, json, basePath, callback); },
            function(callback) { handleAll('meshes'     , handleMesh      , ctx, json, basePath, callback); },
            function(callback) { handleAll('images'     , handleImage     , ctx, json, basePath, callback); },
            function(callback) { handleAll('textures'   , handleTexture   , ctx, json, basePath, callback); },
            function(callback) { handleAll('shaders'    , handleShader    , ctx, json, basePath, callback); },
            function(callback) { handleAll('programs'   , handleProgram   , ctx, json, basePath, callback); },
            function(callback) { handleAll('nodes'      , handleNode      , ctx, json, basePath, callback); },
            function(callback) { buildMeshes(ctx, json, callback); },
        ], function(err, results) {
            if (err) log('load done errors', err);
            else log('load done');
            callback(err, json);
        })
    })
}

module.exports = load;
<commit_msg>Added scene handling<commit_after>var loadJSON   = require('pex-io/loadJSON');
var loadBinary = require('pex-io/loadBinary');
var loadText   = require('pex-io/loadText');
var loadImage  = require('pex-io/loadImage');
var log        = require('debug')('pex/gltf');
var info       = require('debug')('pex/gltf/info');
var path       = require('path');
var async      = require('async');
var iterateObject = require("iterate-object");

var WebGLConstants = {
    34963: 'ELEMENT_ARRAY_BUFFER',  //0x8893
    34962: 'ARRAY_BUFFER',          //0x8892
     5123: 'UNSIGNED_SHORT',        //0x1403
     5126: 'FLOAT',                 //0x1406
        4: 'TRIANGLES',             //0x0004
    35678: 'SAMPLER_2D',            //0x8B5E
    35664: 'FLOAT_VEC2',            //0x8B50
    35665: 'FLOAT_VEC3',            //0x8B51
    35666: 'FLOAT_VEC4',            //0x8B52
    35676: 'FLOAT_MAT4',            //0x8B5C
     5126: 'FLOAT'                  //0x1406
};

function handleBuffer(ctx, json, basePath, bufferName, bufferInfo, callback) {
    log('handleBuffer', bufferName, bufferInfo.uri);
    if (bufferInfo.uri) {
        loadBinary(basePath + '/' + bufferInfo.uri, function(err, data) {
            bufferInfo.arrayBuffer = data; //TODO: obj addon use _
            callback(err, data);
        });
    }
    else {
        throw new Error('gltf/handleBuffer missing uri in ' + JSON.stringify(bufferInfo));
    }
}

function handleBufferView(ctx, json, basePath, bufferViewName, bufferViewInfo, callback) {
   log('handleBufferView', bufferViewName);
   var buffer = json.buffers[bufferViewInfo.buffer];
   bufferViewInfo._typedArray = null;
   if (bufferViewInfo.target == 34963) { //ELEMENT_ARRAY_BUFFER //TODO: ctx constant
      //TODO: Slice or not to slice the buffer
      bufferViewInfo.buffer = buffer.arrayBuffer.slice(bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength); //ADDON
      info('slice', bufferViewName, bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength, '=', bufferViewInfo.buffer.byteLength);
      bufferViewInfo._buffer = bufferViewInfo.buffer;
      bufferViewInfo._typedArray = new Uint16Array(bufferViewInfo.buffer);
   }
   if (bufferViewInfo.target == 34962) { //ARRAY_BUFFER //TODO: ctx constant
      //TODO: Slice or not to slice the buffer
      bufferViewInfo.buffer = buffer.arrayBuffer.slice(bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength);
      info('slice', bufferViewName, bufferViewInfo.byteOffset, bufferViewInfo.byteOffset + bufferViewInfo.byteLength, '=', bufferViewInfo.buffer.byteLength);
      //bufferViewInfo.buffer = buffer.arrayBuffer;
      bufferViewInfo._buffer = bufferViewInfo.buffer;
      bufferViewInfo._typedArray = new Float32Array(bufferViewInfo.buffer);
   }
   log('handleBufferView', bufferViewName, WebGLConstants[bufferViewInfo.target], bufferViewInfo.byteOffset, '..', bufferViewInfo.byteLength, '/', buffer.arrayBuffer.byteLength)
   callback(null, bufferViewInfo);
}

function handleAccessor(ctx, json, basePath, accessorName, accessorInfo, callback) {
    log('handleAccessor', accessorName);
    callback(null, accessorInfo);
}

function linkPrimitive(json, primitiveName, primitiveInfo) {
    log('handlePrimitive', primitiveName)
    primitiveInfo.indices.accessor = json.accessors[primitiveInfo.indices];
    Object.keys(primitiveInfo.attributes).forEach(function(attribute) {
        primitiveInfo.attributes[attribute].accessor = json.accessors[primitiveInfo.attributes[attribute]];
    })
}

function handleMesh(ctx, json, basePath, meshName, meshInfo, callback) {
    log('handleMesh', meshInfo.name);
    meshInfo.primitives.forEach(function(primitiveInfo, primitiveIndex) {
        linkPrimitive(json, primitiveIndex, primitiveInfo);
    })
    callback(null, meshInfo);
}

function buildMeshes(ctx, json, callback) {
    log('buildMeshes');
    var AttributeLocationMap = {
        "POSITION": ctx.ATTRIB_POSITION,
        "NORMAL": ctx.ATTRIB_NORMAL,
        "TEXCOORD_0": ctx.ATTRIB_TEX_COORD_0
    }

    log('buildMesh AttributeNameMap');

    var AttributeSizeMap = {
        "SCALAR": 1,
        "VEC3": 3,
        "VEC2": 2
    }

    function buildBufferInfo(accessorName) {
        var accessorInfo = json.accessors[accessorName];
        var size = AttributeSizeMap[accessorInfo.type];
        //TODO: any other way to limit attrib count?
        var data = json.bufferViews[accessorInfo.bufferView]._typedArray;//.subarray(0, accessorInfo.count * size);
        var buffer = json.bufferViews[accessorInfo.bufferView]._buffer;
        if (buffer) {
            if (json.bufferViews[accessorInfo.bufferView].target == 34963) { //TODO: use ctx
                data = new Uint16Array(buffer.slice(accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 2));
            }
            else {
                data = new Float32Array(buffer.slice(accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 4));
            }
            info('subarray', accessorName, accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 4);
        }
        else {
            info('subarray', accessorName, accessorInfo.byteOffset, accessorInfo.byteOffset + accessorInfo.count * size * 2);
        }

        var bufferInfo = {
            data: data,
            opts: {
                offset: 0,
                stride: accessorInfo.byteStride,
                size: size
            }
        };
        log(bufferInfo.opts);
        return bufferInfo;
    }

    iterateObject(json.meshes, function(meshInfo, meshName, meshIndex) {
        //if (meshIndex != 2) return; //FIXME: TEMP!
        log('buildMesh', meshName);
        meshInfo.primitives.forEach(function(primitiveInfo, primitiveIndex) {
            log('buildPrimitive', primitiveIndex);

            //var va = new VertexArray(ctx); //TODO: use ctx

            var attributes = [];

            iterateObject(primitiveInfo.attributes, function(accessorName, attributeSemantic) {
                var attributeInfo = buildBufferInfo(accessorName);
                var attributeLocation = AttributeLocationMap[attributeSemantic];
                log('buildAttribute', attributeSemantic, attributeInfo.opts);
                //va.addAttribute(attributeName, attributeInfo.data, attributeInfo.opts); //TODO: use ctx
                //
                var vertexBuffer = ctx.createBuffer(ctx.ARRAY_BUFFER, attributeInfo.data, ctx.STATIC_DRAW);
                attributes.push({
                    buffer: vertexBuffer,
                    location: attributeLocation,
                    size: attributeInfo.opts.size,
                    stride: attributeInfo.opts.stride,
                    offset: attributeInfo.opts.offset
                })
            });

            var indexBufferInfo = buildBufferInfo(primitiveInfo.indices);

            log('buildIndexBuffer', indexBufferInfo.opts, 'len:', indexBufferInfo.data.length);
            //va.addIndexBuffer(indexBufferInfo.data, indexBufferInfo.opts); //TODO: use ctx

            var indexBuffer = ctx.createBuffer(ctx.ELEMENT_ARRAY_BUFFER, indexBufferInfo.data, ctx.STATIC_DRAW);

            var va = ctx.createVertexArray(attributes, indexBuffer);

            primitiveInfo.vertexArray = va; //TODO: use ctx

            //* var vertexArray = new VertexArray(ctx,[
            //*     {buffer : buffer0, location : ctx.ATTRIB_POSITION, size : 3, stride : 0, offset : 0 },
            //*     {buffer : buffer0, location : ctx.ATTRIB_NORMAL, size : 3, stride : 0, offset : 4 * 3 * 4},
            //*     {buffer : buffer1, location : ctx.ATTRIB_COLOR, size : 4},
            //* ], indexBuffer);
        })//
    })

  callback(null, json);
}

function handleShader(ctx, json, basePath, shaderName, shaderInfo, callback) {
    log('handleShader', shaderName);
    if (shaderInfo.uri) {
        loadText(basePath + '/' + shaderInfo.uri, function(err, srcStr) {
            //TODO: add error handling
            log('handleShader');
            //precision is already added in Program class
            shaderInfo._src = srcStr.replace('precision highp float;', ''); //TODO: is it?
            callback(err, shaderInfo);
        });
    }
    else {
        throw new Error('gltf/handleShader missing uri in ' + JSON.stringify(shaderInfo));
    }
}

function handleImage(ctx, json, basePath, imageName, imageInfo, callback) {
    log('handleImage', imageInfo.uri);
    if (imageInfo.uri) {
        var url = basePath + '/' + imageInfo.uri;
        loadImage(url, function(err, img) {
            if (err) {
                log('handleImage', err.toString());
                callback(err, null);
            }
            else {
                imageInfo._img = img;
                callback(null, img);
            }
        })
    }
    else {
        throw new Error('gltf/handleImage missing uri in ' + JSON.stringify(imageInfo));
    }
}

function handleTexture(ctx, json, basePath, textureName, textureInfo, callback) {
    log('handleTexture', textureInfo.source)
    if (textureInfo.source) {
        var img = json.images[textureInfo.source]._img;
        var opts = {
            magFilter: ctx.NEAREST,
            minFilter: ctx.NEAREST
        }
        textureInfo._texture = ctx.createTexture2D(img, img.width, img.height, opts);
        callback(null, textureInfo);
    }
    else {
        throw new Error('gltf/handleTexture missing uri in ' + JSON.stringify(textureInfo));
    }
}

function handleProgram(ctx, json, basePath, programName, programInfo, callback) {
    log('handleProgram', programName);
    var vertSrc = json.shaders[programInfo.vertexShader]._src;
    var fragSrc = json.shaders[programInfo.fragmentShader]._src;
    //FIXME: hardcoded
    vertSrc = vertSrc.replace(/a_position/g, 'position');
    vertSrc = vertSrc.replace(/a_normal/g, 'normal');
    vertSrc = vertSrc.replace(/a_texcoord0/g, 'texcoord');
    programInfo._program = ctx.createProgram(vertSrc, fragSrc);
    callback(null, programInfo);
}

function handleNode(ctx, json, basePath, nodeName, nodeInfo, callback) {
    log('handleNode', nodeName);
    //FIXME: solve that with Ramda partial
    nodeInfo.children = nodeInfo.children.map(function(childNodeName) {
        json.nodes[childNodeName].parent = nodeInfo;
        return json.nodes[childNodeName];
    })
    callback(null, nodeInfo);
}

function handleScene(ctx, json, basePath, sceneName, sceneInfo, callback) {
    log('handleScene', sceneName);
    //FIXME: solve that with Ramda partial
    sceneInfo.nodes = sceneInfo.nodes.map(function(childNodeName) {
        return json.nodes[childNodeName];
    })
    callback(null, sceneInfo);
}

function handleAll(typeName, handler, ctx, json, basePath, callback) {
    log('handleAll', typeName);
    if (!json[typeName]) {
        log('missing', typeName);
        return callback(null, null);
    }
    async.map(
        Object.keys(json[typeName]),
        function(nodeName, callback) {
            handler(ctx, json, basePath, nodeName, json[typeName][nodeName], callback);
        },
        callback
    )
}

function load(ctx, file, callback) {
    var basePath = path.dirname(file);
    log('load ', file);
    loadJSON(file, function(err, json) {
        if (err) {
            console.log('loadJSON', err)
            return callback(err, null);
        }
        async.series([
            function(callback) { handleAll('buffers'    , handleBuffer    , ctx, json, basePath, callback); },
            function(callback) { handleAll('bufferViews', handleBufferView, ctx, json, basePath, callback); },
            function(callback) { handleAll('accessors'  , handleAccessor  , ctx, json, basePath, callback); },
            function(callback) { handleAll('meshes'     , handleMesh      , ctx, json, basePath, callback); },
            function(callback) { handleAll('images'     , handleImage     , ctx, json, basePath, callback); },
            function(callback) { handleAll('textures'   , handleTexture   , ctx, json, basePath, callback); },
            function(callback) { handleAll('shaders'    , handleShader    , ctx, json, basePath, callback); },
            function(callback) { handleAll('programs'   , handleProgram   , ctx, json, basePath, callback); },
            function(callback) { handleAll('nodes'      , handleNode      , ctx, json, basePath, callback); },
            function(callback) { handleAll('scenes'     , handleScene     , ctx, json, basePath, callback); },
            function(callback) { buildMeshes(ctx, json, callback); },
        ], function(err, results) {
            if (err) log('load done errors', err);
            else log('load done');
            callback(err, json);
        })
    })
}

module.exports = load;
<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

module.exports = {
    id: 'android',
    bootstrap: function() {
        var channel = require('cordova/channel'),
            cordova = require('cordova'),
            exec = require('cordova/exec'),
            modulemapper = require('cordova/modulemapper');

        // Get the shared secret needed to use the bridge.
        exec.init();

        // TODO: Extract this as a proper plugin.
        modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');

        var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';

        // Inject a listener for the backbutton on the document.
        var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
        backButtonChannel.onHasSubscribersChange = function() {
            // If we just attached the first handler or detached the last handler,
            // let native know we need to override the back button.
            exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
        };

        // Add hardware MENU and SEARCH button handlers
        cordova.addDocumentEventHandler('menubutton');
        cordova.addDocumentEventHandler('searchbutton');

        function bindButtonChannel(buttonName) {
            // generic button bind used for volumeup/volumedown buttons
            var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
            volumeButtonChannel.onHasSubscribersChange = function() {
                exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
            };
        }
        // Inject a listener for the volume buttons on the document.
        bindButtonChannel('volumeup');
        bindButtonChannel('volumedown');

        // Let native code know we are all done on the JS side.
        // Native code will then un-hide the WebView.
        channel.onCordovaReady.subscribe(function() {
            exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
            exec(null, null, APP_PLUGIN_NAME, "show", []);
        });
    }
};

function onMessageFromNative(msg) {
    var cordova = require('cordova');
    var action = msg.action;

    switch (action)
    {
        // Button events
        case 'backbutton':
        case 'menubutton':
        case 'searchbutton':
        // App life cycle events
        case 'pause':
        case 'resume':
        // Keyboard events
        case 'hidekeyboard':
        case 'showkeyboard':
        // Volume events
        case 'volumedownbutton':
        case 'volumeupbutton':
            cordova.fireDocumentEvent(action);
            break;
        default:
            throw new Error('Unknown event action ' + action);
    }
}
<commit_msg>CB-8378 android: Deleted hidekeyboard & showkeyboard events<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

module.exports = {
    id: 'android',
    bootstrap: function() {
        var channel = require('cordova/channel'),
            cordova = require('cordova'),
            exec = require('cordova/exec'),
            modulemapper = require('cordova/modulemapper');

        // Get the shared secret needed to use the bridge.
        exec.init();

        // TODO: Extract this as a proper plugin.
        modulemapper.clobbers('cordova/plugin/android/app', 'navigator.app');

        var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App';

        // Inject a listener for the backbutton on the document.
        var backButtonChannel = cordova.addDocumentEventHandler('backbutton');
        backButtonChannel.onHasSubscribersChange = function() {
            // If we just attached the first handler or detached the last handler,
            // let native know we need to override the back button.
            exec(null, null, APP_PLUGIN_NAME, "overrideBackbutton", [this.numHandlers == 1]);
        };

        // Add hardware MENU and SEARCH button handlers
        cordova.addDocumentEventHandler('menubutton');
        cordova.addDocumentEventHandler('searchbutton');

        function bindButtonChannel(buttonName) {
            // generic button bind used for volumeup/volumedown buttons
            var volumeButtonChannel = cordova.addDocumentEventHandler(buttonName + 'button');
            volumeButtonChannel.onHasSubscribersChange = function() {
                exec(null, null, APP_PLUGIN_NAME, "overrideButton", [buttonName, this.numHandlers == 1]);
            };
        }
        // Inject a listener for the volume buttons on the document.
        bindButtonChannel('volumeup');
        bindButtonChannel('volumedown');

        // Let native code know we are all done on the JS side.
        // Native code will then un-hide the WebView.
        channel.onCordovaReady.subscribe(function() {
            exec(onMessageFromNative, null, APP_PLUGIN_NAME, 'messageChannel', []);
            exec(null, null, APP_PLUGIN_NAME, "show", []);
        });
    }
};

function onMessageFromNative(msg) {
    var cordova = require('cordova');
    var action = msg.action;

    switch (action)
    {
        // Button events
        case 'backbutton':
        case 'menubutton':
        case 'searchbutton':
        // App life cycle events
        case 'pause':
        case 'resume':
        // Volume events
        case 'volumedownbutton':
        case 'volumeupbutton':
            cordova.fireDocumentEvent(action);
            break;
        default:
            throw new Error('Unknown event action ' + action);
    }
}
<|endoftext|>


<commit_before>/**
 *  JSUtility - v1.0.1
 *  https://github.com/srivenkatesh92/JavascriptUtility
 *  2017-03-19
 *
 *  Freely distributable
 *  copyright (c) 2017 Srivenkatesh
 *  Email : srivenkatesh123@gmail.com
 *
 */

 function JSUtility(){};

 /**
  * emailValidation
  *
  * This method return true/false depends on valid/invalid
  * valid email : someemail@domain.com
  * invalid email : someemail@domaildasd
  *
  */
 var emailValidation = function(email){
	        var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
	        return re.test(email);
 };

 /**
  * Displaying a number in Indian currency format
  *
  * This method return indian format currency value with decimal appended
  * unformattedValue : 123456.987
  * return : 1,23,456.987
  */

 var INRCurrency = function(unformattedValue){
	 var x=unformattedValue;
	 x=x.toString();
	 var afterPoint = '';
	 if(x.indexOf('.') > 0)
	    afterPoint = x.substring(x.indexOf('.'),x.length);
	 x = Math.floor(x);
	 x=x.toString();
	 var lastThree = x.substring(x.length-3);
	 var otherNumbers = x.substring(0,x.length-3);
	 if(otherNumbers != '')
	     lastThree = ',' + lastThree;
	 var res = otherNumbers.replace(/\B(?=(\d{2})+(?!\d))/g, ",") + lastThree + afterPoint;

	 return res;
 };

 /**
  * Only Allow Alphabets. Eg:  onkeypress="return onlyAlphabets(event,this);"
  *
  */
 var alphaOnly = function (e, t) {
	    try {
	        if (window.event) {
	            var charCode = window.event.keyCode;
	        }
	        else if (e) {
	            var charCode = e.which;
	        }
	        else { return true; }
	        if ((charCode > 64 && charCode < 91) || (charCode > 96 && charCode < 123) || (charCode == 32 || charCode == 46))
	            return true;
	        else
	            return false;
	    }
	    catch (err) {
	        alert(err.Description);
	    }
	}<commit_msg>getRandom method added<commit_after>/**
 *  JSUtility - v1.0.1
 *  https://github.com/srivenkatesh92/JavascriptUtility
 *  2017-03-19
 *
 *  Freely distributable
 *  copyright (c) 2017 Srivenkatesh
 *  Email : srivenkatesh123@gmail.com
 *
 */

 function JSUtility(){};

 /**
  * emailValidation
  *
  * This method return true/false depends on valid/invalid
  * valid email : someemail@domain.com
  * invalid email : someemail@domaildasd
  *
  */
 var emailValidation = function(email){
	        var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
	        return re.test(email);
 };

 /**
  * Displaying a number in Indian currency format
  *
  * This method return indian format currency value with decimal appended
  * unformattedValue : 123456.987
  * return : 1,23,456.987
  */

 var INRCurrency = function(unformattedValue){
	 var x=unformattedValue;
	 x=x.toString();
	 var afterPoint = '';
	 if(x.indexOf('.') > 0)
	    afterPoint = x.substring(x.indexOf('.'),x.length);
	 x = Math.floor(x);
	 x=x.toString();
	 var lastThree = x.substring(x.length-3);
	 var otherNumbers = x.substring(0,x.length-3);
	 if(otherNumbers != '')
	     lastThree = ',' + lastThree;
	 var res = otherNumbers.replace(/\B(?=(\d{2})+(?!\d))/g, ",") + lastThree + afterPoint;

	 return res;
 };

 /**
  * Only Allow Alphabets. Eg:  onkeypress="return onlyAlphabets(event,this);"
  *
  */
 var alphaOnly = function (e, t) {
	    try {
	        if (window.event) {
	            var charCode = window.event.keyCode;
	        }
	        else if (e) {
	            var charCode = e.which;
	        }
	        else { return true; }
	        if ((charCode > 64 && charCode < 91) || (charCode > 96 && charCode < 123) || (charCode == 32 || charCode == 46))
	            return true;
	        else
	            return false;
	    }
	    catch (err) {
	        alert(err.Description);
	    }
	}

 /**
  * getRandom
  *  we are generating lots of random number, and maximum of them are in a range
  *  e.g. generating random number between X to Y.
  */
var getRandom = function (min, max) {  // Adding getRandom function to Math object
	    if (min && max) {
	        // If min and max both are provided
	        if(max <= min) {
	            return new Error("Max number can not be equal or less then min");
	        } else {
	            return parseInt(min + (Math.random() * (max - min))); // Our logic for getting a random number
	        }
	    } else if (min) { //  If only min provide, then that min will be max and min will be 0
	        if(min < 1) {
	            return new Error("Min number can not be less 1");
	        } else {
	            return parseInt(Math.random() * min)
	        }
	    } else { // If min and max both are not provided then return whatever Math.random returns.
	        return Math.random();
	    }
	};<|endoftext|>


<commit_before>'use strict';

angular.module('metadatamanagementApp').controller(
  'ToolbarController',
  function($scope, $rootScope, $mdSidenav, ShoppingCartService, Principal,
           SearchResultNavigatorService, LanguageService, Auth, $state,
           MessageBus, $location) {
    $scope.open = $mdSidenav('SideNavBar').isOpen();
    //Toggle Function
    $scope.toggleLeft = function() {
      $mdSidenav('SideNavBar').toggle();
    };
    $scope.isAuthenticated = Principal.isAuthenticated;
    $scope.hasAuthority = Principal.hasAuthority;

    //Set Languages
    $scope.changeLanguage = function(languageKey) {
      LanguageService.setCurrent(languageKey);
    };

    //Goto Logout Page
    $scope.logout = function() {
      Auth.logout();
      $state.go('search', {
        lang: LanguageService.getCurrentInstantly(),
        type: 'studies'
      }, {
        reload: true
      });
    };
    $scope.resetQuery = function() {
      $rootScope.searchQuery = '';
      var searchParams = $location.search();
      if (searchParams && searchParams.hasOwnProperty('query')) {
        delete searchParams.query;
        $location.search(searchParams);
      }
      MessageBus.remove('searchFilter');
    };
    $scope.resetFilter = function() {
      var search = $location.search();
      var params = {};
      params.type = search.type;
      params.page = 1;
      params.size = search.size;
      $location.search(params);
      MessageBus.remove('searchFilter');
    };
    $scope.productsCount = ShoppingCartService.count();
    $scope.$on('shopping-cart-changed',
      function(event, count) { // jshint ignore:line
        $scope.productsCount = count;
      });

    $scope.SearchResultNavigatorService = SearchResultNavigatorService;
    $scope.$watch(function() {
      return $mdSidenav('SideNavBar').isOpen();
    }, function() {
      $scope.open = $mdSidenav('SideNavBar').isOpen();
    });
  });
<commit_msg>Fix sidenav error<commit_after>'use strict';

angular.module('metadatamanagementApp').controller(
  'ToolbarController',
  function($scope, $rootScope, $mdSidenav, ShoppingCartService, Principal,
           SearchResultNavigatorService, LanguageService, Auth, $state,
           MessageBus, $location, $timeout) {
    $scope.open = false;
    //Toggle Function
    $scope.toggleLeft = function() {
      $mdSidenav('SideNavBar').toggle();
    };
    $scope.isAuthenticated = Principal.isAuthenticated;
    $scope.hasAuthority = Principal.hasAuthority;

    //Set Languages
    $scope.changeLanguage = function(languageKey) {
      LanguageService.setCurrent(languageKey);
    };

    //Goto Logout Page
    $scope.logout = function() {
      Auth.logout();
      $state.go('search', {
        lang: LanguageService.getCurrentInstantly(),
        type: 'studies'
      }, {
        reload: true
      });
    };
    $scope.resetQuery = function() {
      $rootScope.searchQuery = '';
      var searchParams = $location.search();
      if (searchParams && searchParams.hasOwnProperty('query')) {
        delete searchParams.query;
        $location.search(searchParams);
      }
      MessageBus.remove('searchFilter');
    };
    $scope.resetFilter = function() {
      var search = $location.search();
      var params = {};
      params.type = search.type;
      params.page = 1;
      params.size = search.size;
      $location.search(params);
      MessageBus.remove('searchFilter');
    };
    $scope.productsCount = ShoppingCartService.count();
    $scope.$on('shopping-cart-changed',
      function(event, count) { // jshint ignore:line
        $scope.productsCount = count;
      });

    $scope.SearchResultNavigatorService = SearchResultNavigatorService;
    $timeout(function () {
      $scope.$watch(function () {
        return $mdSidenav('SideNavBar').isOpen();
      }, function () {
        $scope.open = $mdSidenav('SideNavBar').isOpen();
      });
    });
  });
<|endoftext|>


<commit_before>import { promisify } from "util"

import redis from "redis"

import { logger } from "~/app/index"
import { isTest } from "~/share/environment"

const REDIS_DB = isTest() ? 7 : 1

export default class Redis {
  constructor() {
    this.redis = redis.createClient({ db: REDIS_DB })
  }

  get set() { return promisify(this.redis.set).bind(this.redis) }
  get get() { return promisify(this.redis.get).bind(this.redis) }

  get hget() { return promisify(this.redis.hget).bind(this.redis) }
  get hgetall() { return promisify(this.redis.hgetall).bind(this.redis) }

  get end() { return this.redis.end.bind(this.redis) }

  get batch() { return this.redis.batch.bind(this.redis) }

  get multi() { return this.redis.multi.bind(this.redis) }

  get incr() { return this.redis.incr.bind(this.redis) }
  get decr() { return this.redis.decr.bind(this.redis) }

  get on() { return this.redis.on.bind(this.redis) }

  subscribe(channel) {
    logger.debug(`[Redis SUB] (${channel})`)

    return this.redis.subscribe(channel)
  }

  unsubscribe(channel) {
    logger.debug(`[Redis SUB] (${channel})`)

    return this.redis.unsubscribe(channel)
  }

  publish(channel, message) {
    logger.debug(`[Redis PUB] (${channel}) (${message})`)

    return this.redis.publish(channel, message)
  }
}
<commit_msg>log correct event<commit_after>import { promisify } from "util"

import redis from "redis"

import { logger } from "~/app/index"
import { isTest } from "~/share/environment"

const REDIS_DB = isTest() ? 7 : 1

export default class Redis {
  constructor() {
    this.redis = redis.createClient({ db: REDIS_DB })
  }

  get set() { return promisify(this.redis.set).bind(this.redis) }
  get get() { return promisify(this.redis.get).bind(this.redis) }

  get hget() { return promisify(this.redis.hget).bind(this.redis) }
  get hgetall() { return promisify(this.redis.hgetall).bind(this.redis) }

  get end() { return this.redis.end.bind(this.redis) }

  get batch() { return this.redis.batch.bind(this.redis) }

  get multi() { return this.redis.multi.bind(this.redis) }

  get incr() { return this.redis.incr.bind(this.redis) }
  get decr() { return this.redis.decr.bind(this.redis) }

  get on() { return this.redis.on.bind(this.redis) }

  subscribe(channel) {
    logger.debug(`[Redis SUB] (${channel})`)

    return this.redis.subscribe(channel)
  }

  unsubscribe(channel) {
    logger.debug(`[Redis UNSUB] (${channel})`)

    return this.redis.unsubscribe(channel)
  }

  publish(channel, message) {
    logger.debug(`[Redis PUB] (${channel}) (${message})`)

    return this.redis.publish(channel, message)
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import styled from 'styled-components'
import Home from './Main/Home/index'
import Feeds from './Server/Feeds/index'
import Settings from './Server/Settings/index'
import Message from './Feed/Message/index'
import Filters from './Feed/Filters/index'
import Subscriptions from './Feed/Subscriptions/index'
import MiscOptions from './Feed/MiscOptions/index'
import Debugger from './Feed/Debugger/index'
import { Switch, Route } from 'react-router-dom'
import { connect } from 'react-redux'
import { setActiveGuild } from '../../../actions/index-actions'
import pages from '../../../constants/pages';
import { changePage } from '../../../actions/index-actions'
import { Scrollbars } from 'react-custom-scrollbars';

const mapDispatchToProps = dispatch => {
  return {
    setActiveGuild: guildId => dispatch(setActiveGuild(guildId)),
    changePage: page => dispatch(changePage(page))
  }
}

const Body = styled.div`
  height: 100%;
  width: 100%;
  background-color: #202225;
  /* overflow-y: auto; */
  /* scrollbar-width: thin; */
`

class ContentBody extends Component {
  constructor () {
    super()
    this.state = {

    }
  }

  redirect = page => {
    const { changePage } = this.props
    changePage(page)
    this.props.history.push(page)
  }

  render () {
    return (
      <Body>
        {/* <Notice>Hello!</Notice> */}
        <Scrollbars>
          <Switch>

            <Route exact path={pages.DASHBOARD} render={routerProps => <Home redirect={this.redirect} {...routerProps} />}/>
            <Route exact path={pages.FEEDS} render={routerProps => <Feeds redirect={this.redirect} {...routerProps} />} />
            <Route exact path={pages.SERVER_SETTINGS} render={routerProps => <Settings {...routerProps} />} />
            <Route exact path={pages.MESSAGE} render={routerProps => <Message {...routerProps} />} />
            <Route exact path={pages.FILTERS} render={routerProps => <Filters {...routerProps} />} />
            <Route exact path={pages.SUBSCRIPTIONS} render={routerProps => <Subscriptions {...routerProps} />} />
            <Route exact path={pages.MISC_OPTIONS} render={routerProps => <MiscOptions {...routerProps} />} />
            <Route exact path={pages.DEBUGGER} component={routerProps => <Debugger {...routerProps} />} />
            <Route render={routerProps => <Home {...routerProps} />} />
          </Switch>
        </Scrollbars>
      </Body>
    )
  }
}

export default withRouter(connect(null, mapDispatchToProps)(ContentBody))
<commit_msg>Convert ContentBody index to hooks<commit_after>import React from 'react'
import { useHistory } from 'react-router-dom'
import styled from 'styled-components'
import Home from './Main/Home/index'
import Feeds from './Server/Feeds/index'
import Settings from './Server/Settings/index'
import Message from './Feed/Message/index'
import Filters from './Feed/Filters/index'
import Subscriptions from './Feed/Subscriptions/index'
import MiscOptions from './Feed/MiscOptions/index'
import Debugger from './Feed/Debugger/index'
import { Switch, Route } from 'react-router-dom'
import { useDispatch } from 'react-redux'
import pages from '../../../constants/pages';
import { Scrollbars } from 'react-custom-scrollbars';
import { changePage } from 'js/actions/page'

const Body = styled.div`
  height: 100%;
  width: 100%;
  background-color: #202225;
  /* overflow-y: auto; */
  /* scrollbar-width: thin; */
`

function ContentBody () {
  const history = useHistory()
  const dispatch = useDispatch()

  function redirect (page) {
    dispatch(changePage(page))
    history.push(page)
  }

  return (
    <Body>
      {/* <Notice>Hello!</Notice> */}
      <Scrollbars>
        <Switch>

          <Route exact path={pages.DASHBOARD} render={routerProps => <Home redirect={redirect} {...routerProps} />}/>
          <Route exact path={pages.FEEDS} render={routerProps => <Feeds redirect={redirect} {...routerProps} />} />
          <Route exact path={pages.SERVER_SETTINGS} render={routerProps => <Settings {...routerProps} />} />
          <Route exact path={pages.MESSAGE} render={routerProps => <Message {...routerProps} />} />
          <Route exact path={pages.FILTERS} render={routerProps => <Filters {...routerProps} />} />
          <Route exact path={pages.SUBSCRIPTIONS} render={routerProps => <Subscriptions {...routerProps} />} />
          <Route exact path={pages.MISC_OPTIONS} render={routerProps => <MiscOptions {...routerProps} />} />
          <Route exact path={pages.DEBUGGER} component={routerProps => <Debugger {...routerProps} />} />
          <Route render={routerProps => <Home {...routerProps} />} />
        </Switch>
      </Scrollbars>
    </Body>
  )
}

export default ContentBody
<|endoftext|>


<commit_before>/**
 * This file contains all necessary Angular controller definitions for 'frontend.admin.login-history' module.
 *
 * Note that this file should only contain controllers and nothing else.
 */
(function() {
    'use strict';

    angular.module('frontend.snapshots')
        .controller('SnapshotsController', [
            '_','$scope', '$rootScope','$q','$log','$ngBootbox','UserModel',
            'SocketHelperService','UserService','SettingsService','MessageService',
            '$state','$uibModal','DialogService','Snapshot','$localStorage',
            'ListConfig','$base64',
            function controller(_,$scope, $rootScope,$q,$log,$ngBootbox,UserModel,
                                SocketHelperService, UserService,SettingsService, MessageService,
                                $state, $uibModal,DialogService,Snapshot,$localStorage,
                                ListConfig,$base64) {

                Snapshot.setScope($scope, false, 'items', 'itemCount');

                // Add default list configuration variable to current scope
                $scope = angular.extend($scope, angular.copy(ListConfig.getConfig()));

                // Set initial data
                $scope.user = UserService.user();

                // Initialize used title items
                $scope.titleItems = ListConfig.getTitleItems('snapshot');

                // Initialize default sort data

                $scope.paging = {
                    currentPage: 1,
                };

                $scope.sort = {
                    column: 'createdAt',
                    direction: false,
                };

                // Initialize filters
                $scope.filters = {
                    searchWord: '',
                    columns: $scope.titleItems,
                };

                // Function to change sort column / direction on list
                $scope.changeSort = function changeSort(item) {
                    var sort = $scope.sort;

                    if (sort.column === item.column) {
                        sort.direction = !sort.direction;
                    } else {
                        sort.column = item.column;
                        sort.direction = true;
                    }

                    _triggerFetchData();
                };

                $scope.pageChanged = function() {
                    $log.log('Page changed to: ' + $scope.paging.currentPage);
                    _fetchData();
                }

                /**
                 * Simple watcher for 'itemsPerPage' scope variable. If this is changed we need to fetch data
                 * from server.
                 */
                $scope.$watch('itemsPerPage', function watcher(valueNew, valueOld) {
                    if (valueNew !== valueOld) {
                        _triggerFetchData();
                    }
                });

                $scope.$watch('filters', function watcher(valueNew, valueOld) {
                    if (valueNew !== valueOld) {
                        _triggerFetchData();
                    }
                },true);

                $scope.takeSnapshot = function(){

                    var modalInstance = $uibModal.open({
                        animation: true,
                        ariaLabelledBy: 'modal-title',
                        ariaDescribedBy: 'modal-body',
                        templateUrl: 'js/app/snapshots/take-snapshot-modal.html',
                        size : 'sm',
                        backdrop: 'static',
                        keyboard: false,
                        controller: function($scope,$rootScope,$log,$uibModalInstance,SnapshotsService,MessageService,UserService) {

                            $scope.user = UserService.user()
                            $scope.node = $scope.user.node;
                            $scope.snapshot = {
                                name : ''
                            }
                            $scope.close = function(){
                                $uibModalInstance.dismiss()
                            }

                            $scope.submit = function() {

                                $scope.error = '';

                                if(!$scope.snapshot.name) {
                                    $scope.error = 'Name is required'
                                    return false;
                                }

                                $scope.submitting = true;
                                SnapshotsService.takeSnapshot($scope.snapshot.name,$scope.node.id)
                                    .then(function(response){
                                        MessageService.success('Snapshot creation may take some time. You will get notified when the job is done!');
                                        $scope.submitting = false;
                                        // $uibModalInstance.dismiss({
                                        //     result : response.data
                                        // })
                                        $uibModalInstance.dismiss({
                                            result : response.data
                                        })
                                    }).catch(function(err){
                                    $scope.submitting = false;
                                    if(err.data && err.data.message) {
                                        $scope.error = err.data.message
                                    }
                                })
                            }
                        }
                    });

                    modalInstance.result.then(function (data) {
                    }, function (data) {
                        if(data && data.result) _triggerFetchData()
                    });

                }

                // User delete dialog buttons configuration
                $scope.confirmButtonsDelete = {
                    ok: {
                        label: 'Delete',
                        className: 'btn-danger btn-link',
                        callback: function callback(result,node) {
                            console.log(node)
                            //$scope.deleteNode();
                        }
                    },
                    cancel: {
                        label: 'Cancel',
                        className: 'btn-default btn-link'
                    }
                };

                $scope.deleteSnapshot = function deleteSnapshot(snapshot) {

                    Snapshot
                        .delete(snapshot.id)
                        .then(
                            function onSuccess(data) {
                                MessageService.success('Snapshot deleted successfully');
                                _triggerFetchData()
                            }
                        )
                    ;
                };

                function _triggerFetchData() {
                    if ($scope.paging.currentPage === 1) {
                        _fetchData();
                    } else {
                        $scope.paging.currentPage = 1;
                    }
                }

                /**
                 * Helper function to fetch actual data for GUI from backend server with current parameters:
                 *  1) Current page
                 *  2) Search word
                 *  3) Sort order
                 *  4) Items per page
                 *
                 * Actually this function is doing two request to backend:
                 *  1) Data count by given filter parameters
                 *  2) Actual data fetch for current page with filter parameters
                 *
                 * These are fetched via 'AuthorModel' service with promises.
                 *
                 * @private
                 */
                function _fetchData() {
                    $scope.loading = true;

                    // Common parameters for count and data query
                    var commonParameters = {
                        where: SocketHelperService.getWhere($scope.filters)
                    };

                    // Data query specified parameters
                    var parameters = {
                        limit: $scope.itemsPerPage,
                        skip: ($scope.paging.currentPage - 1) * $scope.itemsPerPage,
                        sort: $scope.sort.column + ' ' + ($scope.sort.direction ? 'ASC' : 'DESC')
                    };

                    // Fetch data count
                    var count = Snapshot
                        .count(commonParameters)
                        .then(
                            function onSuccess(response) {
                                $scope.itemCount = response.count;
                            }
                        );

                    // Fetch actual data
                    var load = Snapshot
                        .load(_.merge({}, commonParameters, parameters))
                        .then(
                            function onSuccess(response) {
                                $scope.snapshots = response;
                                $log.debug("Snapshots",$scope.snapshots)
                            }
                        )
                        ;

                    // And wrap those all to promise loading
                    $q
                        .all([count, load])
                        .finally(
                            function onFinally() {
                                $scope.loaded = true;
                                $scope.loading = false;
                            }
                        )
                    ;
                }

                $scope.importSnapshot = function() {
                    $("#import-snapshot").click()
                }

                $scope.onSnapshotLoaded = function(data) {

                    var importedSnapshot;

                    try{
                        importedSnapshot = JSON.parse($base64.decode($scope.file.data.replace("data:;base64,","")))
                    }catch(err){
                        MessageService.error(err)
                    }

                    if(!importedSnapshot) return false;

                    // Add timestamp to snapshot name to prevent conflicts and remove it's id and timestamps
                    importedSnapshot.name = new Date().getTime() + "_" + importedSnapshot.name
                    delete importedSnapshot.id;
                    delete importedSnapshot.createdAt;
                    delete importedSnapshot.updatedAt;

                    Snapshot.create(importedSnapshot)
                        .then(function(){
                            _triggerFetchData()
                        }).catch(function(err){
                        // ToDo better error handling
                        MessageService.error("Could not upload snapshot. Make sure the data format is correct.")
                    })

                }

                $scope.$on('snapshots.created',function(ev,message){
                    // MessageService.success("Snapshot created!")
                    _triggerFetchData();
                })

                _triggerFetchData();
            }
        ])
    ;
}());<commit_msg>Fix snapshots upload<commit_after>/**
 * This file contains all necessary Angular controller definitions for 'frontend.admin.login-history' module.
 *
 * Note that this file should only contain controllers and nothing else.
 */
(function() {
    'use strict';

    angular.module('frontend.snapshots')
        .controller('SnapshotsController', [
            '_','$scope', '$rootScope','$q','$log','$ngBootbox','UserModel',
            'SocketHelperService','UserService','SettingsService','MessageService',
            '$state','$uibModal','DialogService','Snapshot','$localStorage',
            'ListConfig','$base64',
            function controller(_,$scope, $rootScope,$q,$log,$ngBootbox,UserModel,
                                SocketHelperService, UserService,SettingsService, MessageService,
                                $state, $uibModal,DialogService,Snapshot,$localStorage,
                                ListConfig,$base64) {

                Snapshot.setScope($scope, false, 'items', 'itemCount');

                // Add default list configuration variable to current scope
                $scope = angular.extend($scope, angular.copy(ListConfig.getConfig()));

                // Set initial data
                $scope.user = UserService.user();

                // Initialize used title items
                $scope.titleItems = ListConfig.getTitleItems('snapshot');

                // Initialize default sort data

                $scope.paging = {
                    currentPage: 1,
                };

                $scope.sort = {
                    column: 'createdAt',
                    direction: false,
                };

                // Initialize filters
                $scope.filters = {
                    searchWord: '',
                    columns: $scope.titleItems,
                };

                // Function to change sort column / direction on list
                $scope.changeSort = function changeSort(item) {
                    var sort = $scope.sort;

                    if (sort.column === item.column) {
                        sort.direction = !sort.direction;
                    } else {
                        sort.column = item.column;
                        sort.direction = true;
                    }

                    _triggerFetchData();
                };

                $scope.pageChanged = function() {
                    $log.log('Page changed to: ' + $scope.paging.currentPage);
                    _fetchData();
                }

                /**
                 * Simple watcher for 'itemsPerPage' scope variable. If this is changed we need to fetch data
                 * from server.
                 */
                $scope.$watch('itemsPerPage', function watcher(valueNew, valueOld) {
                    if (valueNew !== valueOld) {
                        _triggerFetchData();
                    }
                });

                $scope.$watch('filters', function watcher(valueNew, valueOld) {
                    if (valueNew !== valueOld) {
                        _triggerFetchData();
                    }
                },true);

                $scope.takeSnapshot = function(){

                    var modalInstance = $uibModal.open({
                        animation: true,
                        ariaLabelledBy: 'modal-title',
                        ariaDescribedBy: 'modal-body',
                        templateUrl: 'js/app/snapshots/take-snapshot-modal.html',
                        size : 'sm',
                        backdrop: 'static',
                        keyboard: false,
                        controller: function($scope,$rootScope,$log,$uibModalInstance,SnapshotsService,MessageService,UserService) {

                            $scope.user = UserService.user()
                            $scope.node = $scope.user.node;
                            $scope.snapshot = {
                                name : ''
                            }
                            $scope.close = function(){
                                $uibModalInstance.dismiss()
                            }

                            $scope.submit = function() {

                                $scope.error = '';

                                if(!$scope.snapshot.name) {
                                    $scope.error = 'Name is required'
                                    return false;
                                }

                                $scope.submitting = true;
                                SnapshotsService.takeSnapshot($scope.snapshot.name,$scope.node.id)
                                    .then(function(response){
                                        MessageService.success('Snapshot creation may take some time. You will get notified when the job is done!');
                                        $scope.submitting = false;
                                        // $uibModalInstance.dismiss({
                                        //     result : response.data
                                        // })
                                        $uibModalInstance.dismiss({
                                            result : response.data
                                        })
                                    }).catch(function(err){
                                    $scope.submitting = false;
                                    if(err.data && err.data.message) {
                                        $scope.error = err.data.message
                                    }
                                })
                            }
                        }
                    });

                    modalInstance.result.then(function (data) {
                    }, function (data) {
                        if(data && data.result) _triggerFetchData()
                    });

                }

                // User delete dialog buttons configuration
                $scope.confirmButtonsDelete = {
                    ok: {
                        label: 'Delete',
                        className: 'btn-danger btn-link',
                        callback: function callback(result,node) {
                            console.log(node)
                            //$scope.deleteNode();
                        }
                    },
                    cancel: {
                        label: 'Cancel',
                        className: 'btn-default btn-link'
                    }
                };

                $scope.deleteSnapshot = function deleteSnapshot(snapshot) {

                    Snapshot
                        .delete(snapshot.id)
                        .then(
                            function onSuccess(data) {
                                MessageService.success('Snapshot deleted successfully');
                                _triggerFetchData()
                            }
                        )
                    ;
                };

                function _triggerFetchData() {
                    if ($scope.paging.currentPage === 1) {
                        _fetchData();
                    } else {
                        $scope.paging.currentPage = 1;
                    }
                }

                /**
                 * Helper function to fetch actual data for GUI from backend server with current parameters:
                 *  1) Current page
                 *  2) Search word
                 *  3) Sort order
                 *  4) Items per page
                 *
                 * Actually this function is doing two request to backend:
                 *  1) Data count by given filter parameters
                 *  2) Actual data fetch for current page with filter parameters
                 *
                 * These are fetched via 'AuthorModel' service with promises.
                 *
                 * @private
                 */
                function _fetchData() {
                    $scope.loading = true;

                    // Common parameters for count and data query
                    var commonParameters = {
                        where: SocketHelperService.getWhere($scope.filters)
                    };

                    // Data query specified parameters
                    var parameters = {
                        limit: $scope.itemsPerPage,
                        skip: ($scope.paging.currentPage - 1) * $scope.itemsPerPage,
                        sort: $scope.sort.column + ' ' + ($scope.sort.direction ? 'ASC' : 'DESC')
                    };

                    // Fetch data count
                    var count = Snapshot
                        .count(commonParameters)
                        .then(
                            function onSuccess(response) {
                                $scope.itemCount = response.count;
                            }
                        );

                    // Fetch actual data
                    var load = Snapshot
                        .load(_.merge({}, commonParameters, parameters))
                        .then(
                            function onSuccess(response) {
                                $scope.snapshots = response;
                                $log.debug("Snapshots",$scope.snapshots)
                            }
                        )
                        ;

                    // And wrap those all to promise loading
                    $q
                        .all([count, load])
                        .finally(
                            function onFinally() {
                                $scope.loaded = true;
                                $scope.loading = false;
                            }
                        )
                    ;
                }

                $scope.importSnapshot = function() {
                    $("#import-snapshot").click()
                }

                $scope.onSnapshotLoaded = function(data) {

                    var importedSnapshot;

                    try{
                        importedSnapshot = JSON.parse($base64.decode($scope.file.data.split(",")[1]))
                    }catch(err){
                        MessageService.error(err)
                    }

                    if(!importedSnapshot) return false;

                    // Add timestamp to snapshot name to prevent conflicts and remove it's id and timestamps
                    importedSnapshot.name = new Date().getTime() + "_" + importedSnapshot.name
                    delete importedSnapshot.id;
                    delete importedSnapshot.createdAt;
                    delete importedSnapshot.updatedAt;

                    Snapshot.create(importedSnapshot)
                        .then(function(){
                            _triggerFetchData()
                        }).catch(function(err){
                        // ToDo better error handling
                        MessageService.error("Could not upload snapshot. Make sure the data format is correct.")
                    })

                }

                $scope.$on('snapshots.created',function(ev,message){
                    // MessageService.success("Snapshot created!")
                    _triggerFetchData();
                })

                _triggerFetchData();
            }
        ])
    ;
}());<|endoftext|>


<commit_before>$(document).ready(function() {
  const favicon = document.querySelector('[rel=icon]');

  if (favicon && favicon.href) {
    $('.favicon-ok').removeClass('d-none');
    $('favicon-href').html(favicon.href);
  } else {
    $('.favicon-not-ok').removeClass('d-none');
  }
});
<commit_msg>Add missing class to favicon href on status.js<commit_after>$(document).ready(function() {
  const favicon = document.querySelector('[rel=icon]');

  if (favicon && favicon.href) {
    $('.favicon-ok').removeClass('d-none');
    $('.favicon-href').html(favicon.href);
  } else {
    $('.favicon-not-ok').removeClass('d-none');
  }
});
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { useCallback, useMemo, useState } from 'react';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { useDebouncedCallback } from 'use-debounce';
import { Media, Row } from '../form';
import {
  createLink,
  inferLinkType,
  getLinkFromElement,
  LinkType,
} from '../link';
import { useAPI } from '../../app/api';
import { isValidUrl, toAbsoluteUrl, withProtocol } from '../../utils/url';
import { SimplePanel } from './panel';
import { Note, ExpandedTextInput } from './shared';

function LinkPanel({ selectedElements, pushUpdateForObject }) {
  const selectedElement = selectedElements[0];
  const { isFill } = selectedElement;
  const inferredLinkType = useMemo(() => inferLinkType(selectedElement), [
    selectedElement,
  ]);
  const DEFAULT_LINK = createLink({ url: null, icon: null, desc: null });
  const link = useMemo(
    () => getLinkFromElement(selectedElement) || DEFAULT_LINK,
    [selectedElement]
  );
  const canLink = selectedElements.length === 1 && !isFill;

  const [fetchingMetadata, setFetchingMetadata] = useState(false);

  const {
    actions: { getLinkMetadata },
  } = useAPI();

  const [populateMetadata] = useDebouncedCallback((url) => {
    const urlWithProtocol = withProtocol(url);
    if (!isValidUrl(urlWithProtocol)) {
      return;
    }
    setFetchingMetadata(true);
    getLinkMetadata(urlWithProtocol)
      .then(({ title, image }) => {
        pushUpdateForObject(
          'link',
          (prev) => ({
            url: urlWithProtocol,
            desc: title ? title : prev.desc,
            icon: image ? toAbsoluteUrl(urlWithProtocol, image) : prev.icon,
          }),
          DEFAULT_LINK,
          true
        );
      })
      .catch((reason) => {
        if (reason?.code === 'rest_invalid_url') {
          return;
        }
        throw reason;
      })
      .finally(() => {
        setFetchingMetadata(false);
      });
  }, 1200);

  const handleChange = useCallback(
    (properties, submit) => {
      if (properties.url) {
        populateMetadata(properties.url);
      }
      return pushUpdateForObject(
        'link',
        {
          ...properties,
          type: inferredLinkType,
        },
        DEFAULT_LINK,
        submit
      );
    },
    [populateMetadata, pushUpdateForObject, inferredLinkType]
  );

  const handleChangeIcon = useCallback(
    (image) => {
      handleChange({ icon: image.sizes?.medium?.url || image.url }, true);
    },
    [handleChange]
  );

  return (
    <SimplePanel name="link" title={__('Link', 'web-stories')}>
      <Row>
        <Note>
          {__('Enter an address to apply a 1 or 2 tap link', 'web-stories')}
        </Note>
      </Row>

      <Row>
        <ExpandedTextInput
          placeholder={__('Web address', 'web-stories')}
          disabled={!canLink}
          onChange={(value) => handleChange({ url: value })}
          value={link.url || ''}
          clear
        />
      </Row>

      {Boolean(link.url) && link.type === LinkType.TWO_TAP && (
        <Row>
          <ExpandedTextInput
            placeholder={__('Optional description', 'web-stories')}
            disabled={!canLink}
            onChange={(value) => handleChange({ desc: value })}
            value={link.desc || ''}
          />
        </Row>
      )}
      {Boolean(link.url) && link.type === LinkType.TWO_TAP && (
        <Row spaceBetween={false}>
          <Media
            value={link.icon || ''}
            onChange={handleChangeIcon}
            title={__('Select as link icon', 'web-stories')}
            buttonInsertText={__('Select as link icon', 'web-stories')}
            type={'image'}
            size={60}
            loading={fetchingMetadata}
            circle
          />
          {__('Optional brand icon', 'web-stories')}
        </Row>
      )}
    </SimplePanel>
  );
}

LinkPanel.propTypes = {
  selectedElements: PropTypes.array.isRequired,
  pushUpdateForObject: PropTypes.func.isRequired,
};

export default LinkPanel;
<commit_msg>Lint fixes<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { useCallback, useMemo, useState } from 'react';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { useDebouncedCallback } from 'use-debounce';
import { Media, Row } from '../form';
import {
  createLink,
  inferLinkType,
  getLinkFromElement,
  LinkType,
} from '../link';
import { useAPI } from '../../app/api';
import { isValidUrl, toAbsoluteUrl, withProtocol } from '../../utils/url';
import { SimplePanel } from './panel';
import { Note, ExpandedTextInput } from './shared';

function LinkPanel({ selectedElements, pushUpdateForObject }) {
  const selectedElement = selectedElements[0];
  const { isFill } = selectedElement;
  const inferredLinkType = useMemo(() => inferLinkType(selectedElement), [
    selectedElement,
  ]);
  const DEFAULT_LINK = createLink({ url: null, icon: null, desc: null });
  const link = useMemo(
    () => getLinkFromElement(selectedElement) || DEFAULT_LINK,
    [selectedElement, DEFAULT_LINK]
  );
  const canLink = selectedElements.length === 1 && !isFill;

  const [fetchingMetadata, setFetchingMetadata] = useState(false);

  const {
    actions: { getLinkMetadata },
  } = useAPI();

  const [populateMetadata] = useDebouncedCallback((url) => {
    const urlWithProtocol = withProtocol(url);
    if (!isValidUrl(urlWithProtocol)) {
      return;
    }
    setFetchingMetadata(true);
    getLinkMetadata(urlWithProtocol)
      .then(({ title, image }) => {
        pushUpdateForObject(
          'link',
          (prev) => ({
            url: urlWithProtocol,
            desc: title ? title : prev.desc,
            icon: image ? toAbsoluteUrl(urlWithProtocol, image) : prev.icon,
          }),
          DEFAULT_LINK,
          true
        );
      })
      .catch((reason) => {
        if (reason?.code === 'rest_invalid_url') {
          return;
        }
        throw reason;
      })
      .finally(() => {
        setFetchingMetadata(false);
      });
  }, 1200);

  const handleChange = useCallback(
    (properties, submit) => {
      if (properties.url) {
        populateMetadata(properties.url);
      }
      return pushUpdateForObject(
        'link',
        {
          ...properties,
          type: inferredLinkType,
        },
        DEFAULT_LINK,
        submit
      );
    },
    [populateMetadata, pushUpdateForObject, inferredLinkType, DEFAULT_LINK]
  );

  const handleChangeIcon = useCallback(
    (image) => {
      handleChange({ icon: image.sizes?.medium?.url || image.url }, true);
    },
    [handleChange]
  );

  return (
    <SimplePanel name="link" title={__('Link', 'web-stories')}>
      <Row>
        <Note>
          {__('Enter an address to apply a 1 or 2 tap link', 'web-stories')}
        </Note>
      </Row>

      <Row>
        <ExpandedTextInput
          placeholder={__('Web address', 'web-stories')}
          disabled={!canLink}
          onChange={(value) => handleChange({ url: value })}
          value={link.url || ''}
          clear
        />
      </Row>

      {Boolean(link.url) && link.type === LinkType.TWO_TAP && (
        <Row>
          <ExpandedTextInput
            placeholder={__('Optional description', 'web-stories')}
            disabled={!canLink}
            onChange={(value) => handleChange({ desc: value })}
            value={link.desc || ''}
          />
        </Row>
      )}
      {Boolean(link.url) && link.type === LinkType.TWO_TAP && (
        <Row spaceBetween={false}>
          <Media
            value={link.icon || ''}
            onChange={handleChangeIcon}
            title={__('Select as link icon', 'web-stories')}
            buttonInsertText={__('Select as link icon', 'web-stories')}
            type={'image'}
            size={60}
            loading={fetchingMetadata}
            circle
          />
          {__('Optional brand icon', 'web-stories')}
        </Row>
      )}
    </SimplePanel>
  );
}

LinkPanel.propTypes = {
  selectedElements: PropTypes.array.isRequired,
  pushUpdateForObject: PropTypes.func.isRequired,
};

export default LinkPanel;
<|endoftext|>


<commit_before>
var
  express = require('express')
, app = express()
, bodyParser = require('body-parser')
, colors=require("colors/safe")
, port={appPort}
, fs=require('fs-extra')
, path=require('path')
, getApiDataConfig=function(){
	var module = './api_data';
	if(fs.existsSync(module+'.js')){

		// remove cached module
		delete require.cache[require.resolve(module)];
		//reload module
		return require(module);
	}
	return null;
}
,setSingleRouter=function(appInstance,method,routerUrl){

	appInstance[method](routerUrl,function(req,res){
		console.log(colors.gray(' '+method+" "+routerUrl));
		var configs = getApiDataConfig();
		if(configs){
			var methodsArr=configs[routerUrl];
			if(methodsArr){
				var conf=methodsArr[method];
				if(conf){
					//set headers
					if(conf.header){
						for(var hk in conf.header){
							res.setHeader(hk,conf.header[hk]);
						}
					}

					if(conf.res){
						res.send(conf.res);
					}
					else if(conf.fn){
						var rs=conf.fn(req,res);
						if(rs){
							res.send(rs);
						}
					}
				}

			}
		}
		res.end();
	});
}
, router= function(appInstance){
	var configs = getApiDataConfig();

	if(configs){

		for(var routerUrl in configs){

			var methodsArr=configs[routerUrl];
			for(var method in methodsArr){

				var conf=methodsArr[method];

				if(!(conf.res || conf.fn)){
					continue;
				}

				setSingleRouter(appInstance,method,routerUrl);

			}
		}

	}
}
, getFiles=function(dir,bdir) {

    var results = [];
    var baseDir=dir;
    if(bdir){
    	baseDir=bdir;
    }

    fs.readdirSync(dir).forEach(function(file) {

        file = dir+'/'+file;
        var stat = fs.statSync(file);

        if (stat && stat.isDirectory()) {
            results = results.concat(getFiles(file,baseDir))
        } else if(path.extname(file)=='.html'){
        	results.push(file.replace(baseDir,''));
        }

    });

    return results;

};

/* Setup
------------------------------------*/

app.use('/public',express.static(__dirname + '/public'));
app.use('/tests',express.static(__dirname + '/tests'));
app.use('/bower_components',express.static(__dirname + '/bower_components'));
app.use('/compiled_js',express.static(__dirname + '/compiled_js'));

/* Moddileware, mock api request
------------------------------------*/
app.use(bodyParser.json())
app.use(function(req, res, next) {
	router(app);
	next();
});

/* load tests
------------------------------------*/

app.get('/tests/',function(req,res){
	var files=getFiles(__dirname +'/tests');
	var content=[];
	var title="Test List";
	content.push('<!DOCTYPE html>');
	content.push('');
	content.push('	');
	content.push('		<title>');
	content.push(title);
	content.push('		</title>');
	content.push('	');
	content.push('	');
	content.push('');
	content.push('
'+title+'
');
	content.push('
');
	for(var i in files){
		var file=files[i];
		content.push('
');
		content.push(''+file+'');
		content.push('
');
	}
	content.push('
');
	content.push('');
	content.push('');
	res.send(content.join(''));
});

app.get('/',function (req,res) {
	res.redirect('/public');
});

app.listen(port);

console.log(colors.green('- Server is listening on %s\r\n- Press ctrl + c to exit'),port);<commit_msg>support application/x-www-form-urlencoded body parse<commit_after>
var
  express = require('express')
, app = express()
, bodyParser = require('body-parser')
, colors=require("colors/safe")
, port={appPort}
, fs=require('fs-extra')
, path=require('path')
, getApiDataConfig=function(){
	var module = './api_data';
	if(fs.existsSync(module+'.js')){

		// remove cached module
		delete require.cache[require.resolve(module)];
		//reload module
		return require(module);
	}
	return null;
}
,setSingleRouter=function(appInstance,method,routerUrl){

	appInstance[method](routerUrl,function(req,res){
		console.log(colors.gray(' '+method+" "+routerUrl));
		var configs = getApiDataConfig();
		if(configs){
			var methodsArr=configs[routerUrl];
			if(methodsArr){
				var conf=methodsArr[method];
				if(conf){
					//set headers
					if(conf.header){
						for(var hk in conf.header){
							res.setHeader(hk,conf.header[hk]);
						}
					}

					if(conf.res){
						res.send(conf.res);
					}
					else if(conf.fn){
						var rs=conf.fn(req,res);
						if(rs){
							res.send(rs);
						}
					}
				}

			}
		}
		res.end();
	});
}
, router= function(appInstance){
	var configs = getApiDataConfig();

	if(configs){

		for(var routerUrl in configs){

			var methodsArr=configs[routerUrl];
			for(var method in methodsArr){

				var conf=methodsArr[method];

				if(!(conf.res || conf.fn)){
					continue;
				}

				setSingleRouter(appInstance,method,routerUrl);

			}
		}

	}
}
, getFiles=function(dir,bdir) {

    var results = [];
    var baseDir=dir;
    if(bdir){
    	baseDir=bdir;
    }

    fs.readdirSync(dir).forEach(function(file) {

        file = dir+'/'+file;
        var stat = fs.statSync(file);

        if (stat && stat.isDirectory()) {
            results = results.concat(getFiles(file,baseDir))
        } else if(path.extname(file)=='.html'){
        	results.push(file.replace(baseDir,''));
        }

    });

    return results;

};

/* Setup
------------------------------------*/

app.use('/public',express.static(__dirname + '/public'));
app.use('/tests',express.static(__dirname + '/tests'));
app.use('/bower_components',express.static(__dirname + '/bower_components'));
app.use('/compiled_js',express.static(__dirname + '/compiled_js'));

/* Moddileware, mock api request
------------------------------------*/
//support application/json
app.use(bodyParser.json())
//support application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))

app.use(function(req, res, next) {
	router(app);
	next();
});

/* load tests
------------------------------------*/

app.get('/tests/',function(req,res){
	var files=getFiles(__dirname +'/tests');
	var content=[];
	var title="Test List";
	content.push('<!DOCTYPE html>');
	content.push('');
	content.push('	');
	content.push('		<title>');
	content.push(title);
	content.push('		</title>');
	content.push('	');
	content.push('	');
	content.push('');
	content.push('
'+title+'
');
	content.push('
');
	for(var i in files){
		var file=files[i];
		content.push('
');
		content.push(''+file+'');
		content.push('
');
	}
	content.push('
');
	content.push('');
	content.push('');
	res.send(content.join(''));
});

app.get('/',function (req,res) {
	res.redirect('/public');
});

app.listen(port);

console.log(colors.green('- Server is listening on %s\r\n- Press ctrl + c to exit'),port);<|endoftext|>