<commit_before>"use strict";
var fs = require("fs");
var watch = require("../efront/watch");
var path = require("path");
var isObject = require("../basic/isObject");
var lazy = require("../basic/lazy");
var mimes = require("./mime");
var loading_queue = [], loading_count = 0;
var runPromiseInQueue = function () {
    if (loading_count > 2) return;
    if (loading_queue.length) {
        loading_count++;
        loading_queue.shift()();
    }
};
var delPromiseInQueue = function (a) {
    loading_count--;
    runPromiseInQueue();
    return a;
};
var getPromiseInQueue = function (runner) {
    var promise = new Promise(function (ok) {
        loading_queue.push(ok);
    }).then(function () {
        return new Promise(runner);
    }).then(delPromiseInQueue, delPromiseInQueue);
    runPromiseInQueue();
    return promise;
};

var getfileAsync = function (pathname, buffer_size, stat) {
    if (isFinite(buffer_size) && stat.size > buffer_size)
        return getFileHeadAsync(pathname, 0);
    return getPromiseInQueue(function (ok, oh) {
        fs.readFile(pathname, function (error, data) {
            if (error) oh(error);
            else ok(data);
        });
    });
};
var getFileHeadAsync = function (pathname, buffer_size) {
    return getPromiseInQueue(function (ok, oh) {
        fs.open(pathname, "r", function (err, h) {
            if (err) {
                return oh(err);
            }
            var chunk = Buffer.alloc(buffer_size);
            fs.read(h, chunk, 0, chunk.length, 0, function (err, length, chunk) {
                if (err) return fs.close(h), oh(err);
                if (length < chunk.length) {
                    chunk = chunk.slice(0, length);
                }
                chunk.location = pathname;
                fs.close(h, function (error) {
                    if (error) return oh(error);
                    ok(chunk);
                });
            });
        });
    });
};
var statFile = function (pathname) {
    return getPromiseInQueue(function (ok, oh) {
        fs.stat(pathname, function (error, stat) {
            if (error) return oh(error);
            ok(stat);
        });
    });
};

var readdir = function (pathame) {
    return getPromiseInQueue(function (ok, oh) {
        fs.readdir(pathame, { withFileTypes: true }, function (error, files) {
            if (error) return oh(error);
            ok(files);
        });
    });
};

var matcherReg = /[A-Z]+/g;
var replaceCase = function (match, index, input) {
    if (match.length === 1) {
        return "-" + match.toLowerCase();
    }
    if (match.length === 2) {
        match = match.toLowerCase();
        return "-" + match[0] + "-" + match[1];
    }
    if (index + match.length === input.length) {
        return "-" + match;
    }
    return '-' + match.slice(0, match.length - 1) + "-" + match[match.length - 1].toLowerCase();
};

var getExtts = function (extts) {
    if (extts instanceof Array && extts.length <= 1) extts = extts;
    return extts || "";
};
function PackageData(a) {
    Object.assign(this, JSON.parse(a));
}
PackageData.prototype = Object.create(null);

var $pathname = Symbol(":pathname");
var $updateme = Symbol("/update");
var $isloaded = Symbol(":loaded");
var $promised = Symbol(":promised");
var $geturl = Symbol("/getme");
var $buffered = Symbol(":buffer");
var $mtime = Symbol(":mtime");
var $rebuild = Symbol("/rebuild");
var $limit = Symbol(":limit");
var $root = Symbol(":rootpath");
var $linked = Symbol(":linked");
var $checklink = Symbol("/linked");
var $getbuffer = Symbol("/buffer");
function Directory(pathname, rebuild, limit) {
    this[$rebuild] = rebuild;
    this[$limit] = limit;
    this[$pathname] = pathname;
}
Directory.prototype = Object.create(null);
Directory.prototype[$updateme] = async function (updateonly) {
    var that = this;
    var files = await readdir(that[$pathname]);
    var map = {}, changed = {};
    var limit = that[$limit];
    var rebuild = that[$rebuild];
    var pathname = that[$pathname];
    var updated = false;
    var rest = [];
    for (var f of files) {
        map[f.name] = true;
        if (!that[f.name]) {
            updated = true;
            var p = path.join(pathname, f.name);
            var file = that[f.name] = f.isFile() ? new File(p, rebuild, limit) : new Directory(p, rebuild, limit);
            var key = f.name.replace(/\.\w+$/, '');
            changed[key] = true;
            file[$root] = that[$root] || pathname;
        }
        if (/\-/.test(f.name)) {
            rest.push(f);
        }
    }
    for (var f of rest) {
        var newName = f.name.replace(/\-([\s\S])/g, (_, a) => a.toUpperCase());
        if (!map[newName]) {
            map[newName] = true;
            that[newName] = that[f.name];
        }
    }

    for (var k in that) {
        var o = that[k];
        if (o instanceof Directory || o instanceof File) {
            if (!map[k]) {
                delete that[k];
                var key = k.replace(/\.\w+$/, '');
                changed[key] = true;
                updated = true;
            }
        }
    }
    for (var k in that) {
        var key = k.replace(/\.\w+$/, '');
        var o = that[k];
        if (changed[key]) {
            if (o instanceof File && o[$buffered]) {
                delete o[$buffered];
                delete o[$promised];
                delete o[$mtime];
            }
        } else if (updateonly) {
            var file = that[k];
            if (file instanceof File && file[$buffered]) {
                file[$promised] = file[$updateme]();
            }
            else {
                if (file[$isloaded]) {
                    file[$promised] = file[$updateme](updateonly);
                    await file[$promised];
                }
            }
        }
    }
    if (updated && that[$isloaded]) fireUpdate();
    that[$isloaded] = true;
};
Directory.prototype[$geturl] = function (url) {
    var that = this;
    if (!that[$promised]) that[$promised] = that[$updateme]();
    var reload = function () {
        return that[$geturl](url);
    };
    if (!that[$isloaded]) {
        return that[$promised].then(reload);
    }
    var keeys = url.split(/[\\\/]+/);
    var temps = [that];
    var keypath = [''];
    var temp = that;
    search: for (var cx = 0, dx = keeys.length; cx < dx; cx++) {
        var key = keeys[cx];
        if (key === '' || key === '.') continue;
        if (!(key in temp)) {
            let k = key.replace(matcherReg, replaceCase);
            if (k in temp) {
                key = k;
            }
        }
        if (!(key in temp)) {
            if (!that[$limit]) {
                temp = undefined;
                break;
            }
            for (var cy = temps.length - 1; cy >= 0; cy--) {
                if (key in temps[cy]) {
                    let searched = temps[cy][$geturl](keeys.slice(cx).join("/"));
                    if (searched instanceof Promise) {
                        return searched.then(reload);
                    }
                    if (searched !== undefined) {
                        temp = searched;
                        keypath.splice(cy, keypath.length - cy);
                        if (typeof searched === 'string') {
                            keypath.push.apply(keypath, searched.split('/'));
                        } else {
                            keypath.push.apply(keypath, keeys.slice(cx));
                        }
                        temps.splice(cy, keypath.length - cy, temp);
                        break search;
                    }
                }
            }
            continue;
        }

        keypath.push(key);
        temps.push(temp);
        temp = temp[key];
        if (!temp) break;
        if (temp instanceof Directory) {

            if (!temp[$promised]) temp[$promised] = temp[$updateme]();
            if (!temp[$isloaded]) return temp[$promised].then(reload);
            if (temp[$buffered]) break;
        }
    }
    if (temp instanceof File) {
        temp = temp[$getbuffer]();
    }
    else if (temp instanceof Directory && temp[$buffered]) {
        temp = temp[$buffered];
    }

    if (temp instanceof Function) {
        return temp;
    }
    if (temp instanceof Error) {
        return temp;
    }
    if (!isObject(temp)) {
        return temp;
    }
    var curl = keypath.join('/');
    if (temps.length) {
        if (curl.replace(/^\/|\/$/g, '') === keeys.join('/').replace(/^\/|\/$/g, '')) {
            return temp;
        }
        else {
            if (curl.replace(/^[\s\S]*?([^\/]+)\/?$/, "$1") === url.replace(/^[\s\S]*?([^\/]+)\/?$/, "$1")) {
                return curl;
            }
        }
    }
};

var fireUpdate = lazy(function () {
    for (var cx = 0, dx = _reload_handlers.length; cx < dx; cx++) {
        var a = _reload_handlers[cx];
        a();
    }
    var reload = require("../message").reload;
    if (isFunction(reload)) reload();
}, 60);

function File(pathname, rebuild, limit) {
    this[$pathname] = pathname;
    this[$limit] = limit;
    this[$rebuild] = rebuild;
}
File.prototype = Object.create(null);
File.prototype[$checklink] = async function () {
    var that = this;
    var linked = that[$linked];
    if (!linked || !linked.length || !that[$mtime]) return true;
    var mtime = 0;
    for (var cx = 0, dx = linked.length; cx < dx; cx++) {
        var stat = await statFile(linked[cx]);
        if (stat instanceof fs.Stats) {
            mtime += +stat.mtime;
        }
        if (linked !== that[$linked]) return true;
    }
    if (!linked[$mtime]) {
        linked[$mtime] = mtime;
        return true;
    }
    if (mtime !== linked[$mtime]) {
        linked[$mtime] = mtime
        return false;
    }
    return true;
};
File.prototype[$getbuffer] = function () {
    var that = this;
    if (that[$buffered]) return that[$buffered];
    if (that[$promised]) return that[$promised];
    return that[$promised] = that[$updateme]();
};
File.prototype[$updateme] = async function () {
    var that = this;
    var stats = await statFile(that[$pathname]);
    if (!(stats instanceof fs.Stats)) {
        return that[$buffered] = stats;
    }
    if (+stats.mtime === that[$mtime]) {
        if (await that[$checklink]()) {
            return that[$buffered];
        }
    }
    that[$mtime] = +stats.mtime;
    if (that[$buffered]) that[$buffered] = null, fireUpdate();

    var buffer = await getfileAsync(that[$pathname], that[$limit], stats);
    if (that[$rebuild] instanceof Function && buffer instanceof Buffer) {
        var url = path.relative(that[$root], that[$pathname]);
        url = url.replace(/\.(\w+)$/, '');
        try {
            var linked = that[$linked] = [];
            buffer = await that[$rebuild](buffer, url, that[$pathname], that[$linked]);
            if (linked.length) await that[$checklink]();
        } catch (e) {
            buffer = e;
            console.log(e);
            console.error("编译错误:", that[$pathname]);
        }
    }
    if (typeof buffer === "string") buffer = Buffer.from(buffer);
    if (buffer instanceof Buffer) buffer.stat = stats;
    var extend = String(that[$pathname]).match(/\.([^\.]*?)$/);
    buffer.mime = mimes[extend[1]];
    if (!buffer.mime && /^(asp|php|jsp)$/i.test(extend[1])) {
        if (/^\s*\<\!/.test(buffer)) buffer.mime = 'text/html;charset=utf-8';
    }

    return that[$buffered] = buffer;
};

/**
 * 根椐filesroot路径设置文件缓冲
 * @param {string} filesroot
 */
var cache = function (filesroot, rebuild, buffer_size_limit) {
    var sk = function () {
    };
    filesroot = String(filesroot || '').split(",").map(path.normalize);
    var map = {};
    var treeslist = filesroot.filter(fs.existsSync).map((froot) => {
        var froot = fs.realpathSync(froot);
        if (map[froot]) {
            return;
        }
        map[froot] = true;
        if (fs.existsSync(froot) && fs.statSync(froot).isDirectory()) {
            var roots = new Directory(froot, rebuild, isFinite(buffer_size_limit) && buffer_size_limit >= 0 ? buffer_size_limit | 0 : buffer_size_limit);
            watch(froot, lazy(async function () {
                if (roots[$isloaded]) roots[$promised] = roots[$updateme](true);
                await roots[$promised];
            }, 20), true);
            return roots;
        } else {
            return Object.create(null);
        }
    }).filter(a => !!a);
    map = null;
    var seekerAsync = function (url, extts = "") {
        extts = getExtts(extts);
        if (!Array.isArray(extts)) {
            extts = [extts];
        }
        var findPackage = extts.indexOf(".js") >= 0;
        return new Promise(function (ok, oh) {
            var cy = 0;
            var cx = 0;
            var result, matchParent;
            var run = function () {
                if (cx >= extts.length) {
                    cy++;
                    if (cy >= treeslist.length) {
                        if (result === undefined) {
                            if (matchParent) {
                                url = url.replace(/\\/g, '/');
                                if (!/\/$/.test(url)) {
                                    url += '/';
                                    result = url;
                                }
                            }
                        }
                        return ok(result);
                    }
                    cx = 0;
                }
                var tree1 = treeslist[cy];
                var extt = extts[cx];
                var error;
                var promise = tree1[$geturl](url + extt);
                if (promise instanceof Promise) {
                    promise.catch(function (e) {
                        error = e;
                    }).then(function (result) {
                        if (result instanceof Buffer) return ok(result);
                        if (error) oh(error);
                        else run();
                    });
                } else {
                    if (typeof promise === "string" || promise instanceof Buffer || promise instanceof Function) return ok(promise);
                    if (promise instanceof PackageData) return ok(Buffer.from(JSON.stringify(promise)));
                    if (promise instanceof Directory && !result) {
                        if (!findPackage) return ok(promise);
                        var package_file = "package.json";
                        if (package_file in promise) {
                            var file = promise[package_file];
                            if (file instanceof File) {
                                file[$rebuild] = a => a;
                                if (!file[$promised]) file[$promised] = file[$updateme]();
                                if (!file[$buffered]) return file[$promised].then(run, oh);
                                if (file[$buffered] instanceof Buffer) file[$buffered] = new PackageData(file[$buffered]);
                                var package_data = file[$buffered];
                                if (package_data instanceof PackageData && package_data.main) {
                                    var roots = package_data.main.split(/[\/\\]+/).filter(a => a && a !== '.' && !/\:$/.test(a));
                                    if (roots[0] in promise) {
                                        result = path.join(url, roots.join('/')).replace(/\\/g, '/');
                                        return ok(result);
                                    }
                                }
                            }
                        } else {
                            var seek_index = 'index';
                            var index = extts.findIndex(a => seek_index + a in promise);
                            if (~index) {
                                result = path.join(url, seek_index + extts[index]).replace(/\\/g, '/');
                            } else {
                                if (typeof promise === 'string') result = promise;
                            }
                        }
                        matchParent = true;
                    }
                    cx++;
                    run();
                }

            };
            run();
        });
    };
    sk.reset = seekerAsync.reset = function () {
        for (var t of treeslist) {
            delete t[$promised];
        }
    };
    sk.async = seekerAsync;
    return sk;
};
var _reload_handlers = [];
cache.onreload = function (handler) {
    if (handler instanceof Function) _reload_handlers.push(handler);
};
module.exports = cache;<commit_msg>解决无扩展名的文件无法访问的问题<commit_after>"use strict";
var fs = require("fs");
var watch = require("../efront/watch");
var path = require("path");
var isObject = require("../basic/isObject");
var lazy = require("../basic/lazy");
var mimes = require("./mime");
var loading_queue = [], loading_count = 0;
var runPromiseInQueue = function () {
    if (loading_count > 2) return;
    if (loading_queue.length) {
        loading_count++;
        loading_queue.shift()();
    }
};
var delPromiseInQueue = function (a) {
    loading_count--;
    runPromiseInQueue();
    return a;
};
var getPromiseInQueue = function (runner) {
    var promise = new Promise(function (ok) {
        loading_queue.push(ok);
    }).then(function () {
        return new Promise(runner);
    }).then(delPromiseInQueue, delPromiseInQueue);
    runPromiseInQueue();
    return promise;
};

var getfileAsync = function (pathname, buffer_size, stat) {
    if (isFinite(buffer_size) && stat.size > buffer_size)
        return getFileHeadAsync(pathname, 0);
    return getPromiseInQueue(function (ok, oh) {
        fs.readFile(pathname, function (error, data) {
            if (error) oh(error);
            else ok(data);
        });
    });
};
var getFileHeadAsync = function (pathname, buffer_size) {
    return getPromiseInQueue(function (ok, oh) {
        fs.open(pathname, "r", function (err, h) {
            if (err) {
                return oh(err);
            }
            var chunk = Buffer.alloc(buffer_size);
            fs.read(h, chunk, 0, chunk.length, 0, function (err, length, chunk) {
                if (err) return fs.close(h), oh(err);
                if (length < chunk.length) {
                    chunk = chunk.slice(0, length);
                }
                chunk.location = pathname;
                fs.close(h, function (error) {
                    if (error) return oh(error);
                    ok(chunk);
                });
            });
        });
    });
};
var statFile = function (pathname) {
    return getPromiseInQueue(function (ok, oh) {
        fs.stat(pathname, function (error, stat) {
            if (error) return oh(error);
            ok(stat);
        });
    });
};

var readdir = function (pathame) {
    return getPromiseInQueue(function (ok, oh) {
        fs.readdir(pathame, { withFileTypes: true }, function (error, files) {
            if (error) return oh(error);
            ok(files);
        });
    });
};

var matcherReg = /[A-Z]+/g;
var replaceCase = function (match, index, input) {
    if (match.length === 1) {
        return "-" + match.toLowerCase();
    }
    if (match.length === 2) {
        match = match.toLowerCase();
        return "-" + match[0] + "-" + match[1];
    }
    if (index + match.length === input.length) {
        return "-" + match;
    }
    return '-' + match.slice(0, match.length - 1) + "-" + match[match.length - 1].toLowerCase();
};

var getExtts = function (extts) {
    if (extts instanceof Array && extts.length <= 1) extts = extts;
    return extts || "";
};
function PackageData(a) {
    Object.assign(this, JSON.parse(a));
}
PackageData.prototype = Object.create(null);

var $pathname = Symbol(":pathname");
var $updateme = Symbol("/update");
var $isloaded = Symbol(":loaded");
var $promised = Symbol(":promised");
var $geturl = Symbol("/getme");
var $buffered = Symbol(":buffer");
var $mtime = Symbol(":mtime");
var $rebuild = Symbol("/rebuild");
var $limit = Symbol(":limit");
var $root = Symbol(":rootpath");
var $linked = Symbol(":linked");
var $checklink = Symbol("/linked");
var $getbuffer = Symbol("/buffer");
function Directory(pathname, rebuild, limit) {
    this[$rebuild] = rebuild;
    this[$limit] = limit;
    this[$pathname] = pathname;
}
Directory.prototype = Object.create(null);
Directory.prototype[$updateme] = async function (updateonly) {
    var that = this;
    var files = await readdir(that[$pathname]);
    var map = {}, changed = {};
    var limit = that[$limit];
    var rebuild = that[$rebuild];
    var pathname = that[$pathname];
    var updated = false;
    var rest = [];
    for (var f of files) {
        map[f.name] = true;
        if (!that[f.name]) {
            updated = true;
            var p = path.join(pathname, f.name);
            var file = that[f.name] = f.isFile() ? new File(p, rebuild, limit) : new Directory(p, rebuild, limit);
            var key = f.name.replace(/\.\w+$/, '');
            changed[key] = true;
            file[$root] = that[$root] || pathname;
        }
        if (/\-/.test(f.name)) {
            rest.push(f);
        }
    }
    for (var f of rest) {
        var newName = f.name.replace(/\-([\s\S])/g, (_, a) => a.toUpperCase());
        if (!map[newName]) {
            map[newName] = true;
            that[newName] = that[f.name];
        }
    }

    for (var k in that) {
        var o = that[k];
        if (o instanceof Directory || o instanceof File) {
            if (!map[k]) {
                delete that[k];
                var key = k.replace(/\.\w+$/, '');
                changed[key] = true;
                updated = true;
            }
        }
    }
    for (var k in that) {
        var key = k.replace(/\.\w+$/, '');
        var o = that[k];
        if (changed[key]) {
            if (o instanceof File && o[$buffered]) {
                delete o[$buffered];
                delete o[$promised];
                delete o[$mtime];
            }
        } else if (updateonly) {
            var file = that[k];
            if (file instanceof File && file[$buffered]) {
                file[$promised] = file[$updateme]();
            }
            else {
                if (file[$isloaded]) {
                    file[$promised] = file[$updateme](updateonly);
                    await file[$promised];
                }
            }
        }
    }
    if (updated && that[$isloaded]) fireUpdate();
    that[$isloaded] = true;
};
Directory.prototype[$geturl] = function (url) {
    var that = this;
    if (!that[$promised]) that[$promised] = that[$updateme]();
    var reload = function () {
        return that[$geturl](url);
    };
    if (!that[$isloaded]) {
        return that[$promised].then(reload);
    }
    var keeys = url.split(/[\\\/]+/);
    var temps = [that];
    var keypath = [''];
    var temp = that;
    search: for (var cx = 0, dx = keeys.length; cx < dx; cx++) {
        var key = keeys[cx];
        if (key === '' || key === '.') continue;
        if (!(key in temp)) {
            let k = key.replace(matcherReg, replaceCase);
            if (k in temp) {
                key = k;
            }
        }
        if (!(key in temp)) {
            if (!that[$limit]) {
                temp = undefined;
                break;
            }
            for (var cy = temps.length - 1; cy >= 0; cy--) {
                if (key in temps[cy]) {
                    let searched = temps[cy][$geturl](keeys.slice(cx).join("/"));
                    if (searched instanceof Promise) {
                        return searched.then(reload);
                    }
                    if (searched !== undefined) {
                        temp = searched;
                        keypath.splice(cy, keypath.length - cy);
                        if (typeof searched === 'string') {
                            keypath.push.apply(keypath, searched.split('/'));
                        } else {
                            keypath.push.apply(keypath, keeys.slice(cx));
                        }
                        temps.splice(cy, keypath.length - cy, temp);
                        break search;
                    }
                }
            }
            continue;
        }

        keypath.push(key);
        temps.push(temp);
        temp = temp[key];
        if (!temp) break;
        if (temp instanceof Directory) {

            if (!temp[$promised]) temp[$promised] = temp[$updateme]();
            if (!temp[$isloaded]) return temp[$promised].then(reload);
            if (temp[$buffered]) break;
        }
    }
    if (temp instanceof File) {
        temp = temp[$getbuffer]();
    }
    else if (temp instanceof Directory && temp[$buffered]) {
        temp = temp[$buffered];
    }

    if (temp instanceof Function) {
        return temp;
    }
    if (temp instanceof Error) {
        return temp;
    }
    if (!isObject(temp)) {
        return temp;
    }
    var curl = keypath.join('/');
    if (temps.length) {
        if (curl.replace(/^\/|\/$/g, '') === keeys.join('/').replace(/^\/|\/$/g, '')) {
            return temp;
        }
        else {
            if (curl.replace(/^[\s\S]*?([^\/]+)\/?$/, "$1") === url.replace(/^[\s\S]*?([^\/]+)\/?$/, "$1")) {
                return curl;
            }
        }
    }
};

var fireUpdate = lazy(function () {
    for (var cx = 0, dx = _reload_handlers.length; cx < dx; cx++) {
        var a = _reload_handlers[cx];
        a();
    }
    var reload = require("../message").reload;
    if (isFunction(reload)) reload();
}, 60);

function File(pathname, rebuild, limit) {
    this[$pathname] = pathname;
    this[$limit] = limit;
    this[$rebuild] = rebuild;
}
File.prototype = Object.create(null);
File.prototype[$checklink] = async function () {
    var that = this;
    var linked = that[$linked];
    if (!linked || !linked.length || !that[$mtime]) return true;
    var mtime = 0;
    for (var cx = 0, dx = linked.length; cx < dx; cx++) {
        var stat = await statFile(linked[cx]);
        if (stat instanceof fs.Stats) {
            mtime += +stat.mtime;
        }
        if (linked !== that[$linked]) return true;
    }
    if (!linked[$mtime]) {
        linked[$mtime] = mtime;
        return true;
    }
    if (mtime !== linked[$mtime]) {
        linked[$mtime] = mtime
        return false;
    }
    return true;
};
File.prototype[$getbuffer] = function () {
    var that = this;
    if (that[$buffered]) return that[$buffered];
    if (that[$promised]) return that[$promised];
    return that[$promised] = that[$updateme]();
};
File.prototype[$updateme] = async function () {
    var that = this;
    var stats = await statFile(that[$pathname]);
    if (!(stats instanceof fs.Stats)) {
        return that[$buffered] = stats;
    }
    if (+stats.mtime === that[$mtime]) {
        if (await that[$checklink]()) {
            return that[$buffered];
        }
    }
    that[$mtime] = +stats.mtime;
    if (that[$buffered]) that[$buffered] = null, fireUpdate();

    var buffer = await getfileAsync(that[$pathname], that[$limit], stats);
    if (that[$rebuild] instanceof Function && buffer instanceof Buffer) {
        var url = path.relative(that[$root], that[$pathname]);
        url = url.replace(/\.(\w+)$/, '');
        try {
            var linked = that[$linked] = [];
            buffer = await that[$rebuild](buffer, url, that[$pathname], that[$linked]);
            if (linked.length) await that[$checklink]();
        } catch (e) {
            buffer = e;
            console.log(e);
            console.error("编译错误:", that[$pathname]);
        }
    }
    if (typeof buffer === "string") buffer = Buffer.from(buffer);
    if (buffer instanceof Buffer) buffer.stat = stats;
    var extend = String(that[$pathname]).match(/\.([^\.]*)$/);
    if (extend) {
        buffer.mime = mimes[extend[1]];
        if (!buffer.mime && /^(asp|php|jsp)$/i.test(extend[1])) {
            if (/^\s*\<\!/.test(buffer)) buffer.mime = 'text/html;charset=utf-8';
        }
    }

    return that[$buffered] = buffer;
};

/**
 * 根椐filesroot路径设置文件缓冲
 * @param {string} filesroot
 */
var cache = function (filesroot, rebuild, buffer_size_limit) {
    var sk = function () {
    };
    filesroot = String(filesroot || '').split(",").map(path.normalize);
    var map = {};
    var treeslist = filesroot.filter(fs.existsSync).map((froot) => {
        var froot = fs.realpathSync(froot);
        if (map[froot]) {
            return;
        }
        map[froot] = true;
        if (fs.existsSync(froot) && fs.statSync(froot).isDirectory()) {
            var roots = new Directory(froot, rebuild, isFinite(buffer_size_limit) && buffer_size_limit >= 0 ? buffer_size_limit | 0 : buffer_size_limit);
            watch(froot, lazy(async function () {
                if (roots[$isloaded]) roots[$promised] = roots[$updateme](true);
                await roots[$promised];
            }, 20), true);
            return roots;
        } else {
            return Object.create(null);
        }
    }).filter(a => !!a);
    map = null;
    var seekerAsync = function (url, extts = "") {
        extts = getExtts(extts);
        if (!Array.isArray(extts)) {
            extts = [extts];
        }
        var findPackage = extts.indexOf(".js") >= 0;
        return new Promise(function (ok, oh) {
            var cy = 0;
            var cx = 0;
            var result, matchParent;
            var run = function () {
                if (cx >= extts.length) {
                    cy++;
                    if (cy >= treeslist.length) {
                        if (result === undefined) {
                            if (matchParent) {
                                url = url.replace(/\\/g, '/');
                                if (!/\/$/.test(url)) {
                                    url += '/';
                                    result = url;
                                }
                            }
                        }
                        return ok(result);
                    }
                    cx = 0;
                }
                var tree1 = treeslist[cy];
                var extt = extts[cx];
                var error;
                var promise = tree1[$geturl](url + extt);
                if (promise instanceof Promise) {
                    promise.catch(function (e) {
                        error = e;
                    }).then(function (result) {
                        if (result instanceof Buffer) return ok(result);
                        if (error) oh(error);
                        else run();
                    });
                } else {
                    if (typeof promise === "string" || promise instanceof Buffer || promise instanceof Function) return ok(promise);
                    if (promise instanceof PackageData) return ok(Buffer.from(JSON.stringify(promise)));
                    if (promise instanceof Directory && !result) {
                        if (!findPackage) return ok(promise);
                        var package_file = "package.json";
                        if (package_file in promise) {
                            var file = promise[package_file];
                            if (file instanceof File) {
                                file[$rebuild] = a => a;
                                if (!file[$promised]) file[$promised] = file[$updateme]();
                                if (!file[$buffered]) return file[$promised].then(run, oh);
                                if (file[$buffered] instanceof Buffer) file[$buffered] = new PackageData(file[$buffered]);
                                var package_data = file[$buffered];
                                if (package_data instanceof PackageData && package_data.main) {
                                    var roots = package_data.main.split(/[\/\\]+/).filter(a => a && a !== '.' && !/\:$/.test(a));
                                    if (roots[0] in promise) {
                                        result = path.join(url, roots.join('/')).replace(/\\/g, '/');
                                        return ok(result);
                                    }
                                }
                            }
                        } else {
                            var seek_index = 'index';
                            var index = extts.findIndex(a => seek_index + a in promise);
                            if (~index) {
                                result = path.join(url, seek_index + extts[index]).replace(/\\/g, '/');
                            } else {
                                if (typeof promise === 'string') result = promise;
                            }
                        }
                        matchParent = true;
                    }
                    cx++;
                    run();
                }

            };
            run();
        });
    };
    sk.reset = seekerAsync.reset = function () {
        for (var t of treeslist) {
            delete t[$promised];
        }
    };
    sk.async = seekerAsync;
    return sk;
};
var _reload_handlers = [];
cache.onreload = function (handler) {
    if (handler instanceof Function) _reload_handlers.push(handler);
};
module.exports = cache;<|endoftext|>


<commit_before>/**

Standalone implementation of a rules engine.

Author: Nick Grealy
Date: 04/07/2014

TODO:
- use foreach method

 */

var isUndef = function(object){ return typeof object === 'undefined'; };
var isNotUndef = function(object){ return typeof object !== 'undefined'; };
var isFn = function(object){ return typeof object === 'function'; };

var RulesEngine = {

	version  : '1.0.2',
	wildcard : function(){return true},
	all      : function(){return true},
	debug    : false,

    buildEngine: function(inputsMap){
		return this.build(inputsMap.rules, inputsMap.defaultFn, inputsMap.multiMode)
	},

    build: function(twoDimRulesArray, defaultFunction, multiMode){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        if (logDebug){ console.log('-> === Building Rules Engine ===') }
        multiMode = isUndef(multiMode) ? false : multiMode
        var rules = this.buildRules(twoDimRulesArray, defaultFunction)
        return {
			version: thiz.version,
			privRules: rules,
            evaluate: function(inputs){
                var logDebug = isNotUndef(console) && thiz.debug
                if (isUndef(inputs) || inputs == null){
                    if (logDebug){ console.error('Cannot evaluate null or undefined input!') }
                    return
                }
                if (logDebug){ console.log('--- Evaluating inputs= '+thiz.objectToString(inputs)+' ---') }
                var returnResponse = null
				var ruleMatched = false
                for (var i = 0; i < rules.length; i++){
                    if (thiz.equals(rules[i].condition, inputs)){    // if we have a match
						ruleMatched = true
                        if (isNotUndef(rules[i].then)){
                            if (isNotUndef(console)){ console.log('-> Matched rule #' + i + ' - inputs=' + thiz.objectToString(inputs)) }
                            var response = rules[i].then(rules[i].params, inputs)
                            if (logDebug){ console.log('-> Found rule match: ' + rules[i].toString() + ' output:' + response) }
                            returnResponse = response
                        } else {
                            if (logDebug){ console.log('Cannot execute undefined function!') }
                        }
                        if (!multiMode){
                            break
                        }
                    }
                }
                if (!ruleMatched){
                    if (isNotUndef(console)){ console.log('-> No rule matched - inputs=' + thiz.objectToString(inputs)) }
                }
                return returnResponse
            }
        };
    },

    buildRules: function(twoDimensionalArray, defaultFunction){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        var rules = []
        if (isUndef(twoDimensionalArray) || twoDimensionalArray == null || isUndef(twoDimensionalArray.length) || twoDimensionalArray.length == 0){
            throw 'twoDimensionalArray must be a two dimensional array!'
        }
        var tmp = twoDimensionalArray[0] // first row is the list of fields
        var nonStrings = tmp.filter(function(a){ return typeof a !== 'string' })
        if (nonStrings.length > 0){
            var mesg = 'Header row must only contain strings! headers=' + tmp.join(',')
            if (logDebug){ console.error(mesg) }
            throw mesg
        }
        var fieldNames = tmp.filter(function(a){ return a.indexOf && a.indexOf('p_') === -1 })
        var inputNames = tmp.map(function(a){ if (a.indexOf && a.indexOf('p_') === 0){ return a.substring(2) } }).filter(isNotUndef) // this performance could be improved
		if (fieldNames.length == 0){
			throw 'Could not find any field names! headerRow=' + tmp.join(',')
		}
        var expectedLength = fieldNames.length + inputNames.length
        for (var i = 1; i < twoDimensionalArray.length; i++){
            if (isNotUndef(twoDimensionalArray[i])){
                var actualLen = twoDimensionalArray[i].length
                if (actualLen != expectedLength && actualLen != expectedLength+1){
                    throw 'Expected length='+expectedLength+' but found length='+twoDimensionalArray[i].length+'! row#='+i
                }
                var tmp = this.buildRule(fieldNames, inputNames, twoDimensionalArray[i], defaultFunction)
                if (logDebug){ console.log(tmp.toString()) }
                if (isNotUndef(tmp) && tmp !== null){
                    rules[rules.length] = tmp
                }
            } else {
                if (logDebug){ console.log('Rejecting undefined row: #' + i + ' headers=' + fieldNames.join(',')) }
            }
        }
        return rules
    },

    buildRule: function(fieldNames, inputNames, conditions, defaultCondition){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        if (isUndef(defaultCondition)){
            if (logDebug){ console.log('-> Found undefined defaultFunction, replacing with placeholder...') }
            defaultCondition = function(){}
        }
        if (isNotUndef(conditions)){
            var fnlen = fieldNames.length, inlen = inputNames.length
            var conditionObject = {}
            for (var i = 0; i < fnlen; i++){
                conditionObject[fieldNames[i]] = conditions[i]
            }
            var inputObject = {}
            for (var i = 0; i < inlen; i++){
                inputObject[inputNames[i]] = conditions[i + fnlen]
            }
            var thenCondition = defaultCondition
            if ((fnlen + inlen) < conditions.length && isFn(conditions[conditions.length-1])){
                thenCondition = conditions[conditions.length-1]
				if (logDebug){ console.log("-> Using override function.") }
            } else {
				if (logDebug){ console.log("-> Using default function - check1=" + thiz.objectToString(conditions)) }
			}
            return {condition: conditionObject, params: inputObject, then: thenCondition, toString: function(){
                return "Rule={ conditions: " + thiz.objectToString(conditionObject) + ", params: " + thiz.objectToString(inputObject) + ", then: "+ typeof thenCondition+" }"
            }}
        }
    },

    /* -- N.B. These below methods should be in a common library. -- */

    objectToString: function(map){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        var keys = Object.keys(map)
        var string = '('
        if (keys.length > 0){
            string += keys[0]+'='+ (isFn(map[keys[0]]) ? 'function' : map[keys[0]])
        }
        if (keys.length > 1){
            for (var i = 1; i < keys.length; i++){
                string += ','+keys[i]+'='+(isFn(map[keys[i]]) ? 'function' : map[keys[i]])
            }
        }
        string += ')'
        return string
    },

    /* does a deep match, of 1st level fields, of an object */
    equals: function(a, b){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        var keys = Object.keys(a)
        if (logDebug){ console.log('-> Checking keys: ' + keys.join(',')) }
        for (var i = 0; i < keys.length; i++){
            var field = keys[i]
            // check if a is null or undefined, if so, compare using ===
            var aIsNull = isUndef(a[field]) || a[field] === null
            // check if the objects match
            var matches = (aIsNull ? a[field] === b[field] : a[field] == b[field])
                || (isFn(a[field]) && a[field](b[field]))   // function
                || (a[field] instanceof Array && b[field] instanceof Array && a[field].compare(b[field]).equal)   // array
            if (!matches){
                if (logDebug){ console.log('-> Rejecting match on field [rule, actual] -> ' + field + ' [' + a[field] + ' != ' + b[field] + ']') }
                return false;
            } else {
                if (logDebug){ console.log('-> Found match on field [rule, actual] -> ' + field + ' [' + a[field] + ' == ' + b[field] + ']') }
            }
        }
        // everything matches, return true!
        return true;
    }

};
var _re = RulesEngine
<commit_msg>updated rules<commit_after>/**

Standalone implementation of a rules engine.

Author: Nick Grealy
Date: 04/07/2014

 */
var isUndef    = function(object){ return typeof object === 'undefined'; };
var isNotUndef = function(object){ return typeof object !== 'undefined'; };
var isFn       = function(object){ return typeof object === 'function';  };

var RulesEngine = {

	version  : '1.0.2',
	wildcard : function(){return true},
	all      : function(){return true},
	debug    : false,

    buildEngine: function(inputsMap){
		return this.build(inputsMap.rules, inputsMap.defaultFn, inputsMap.multiMode)
	},

    build: function(twoDimRulesArray, defaultFunction, multiMode){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        if (logDebug){ console.log('-> === Building Rules Engine ===') }
        multiMode = isUndef(multiMode) ? false : multiMode
        var rules = this.buildRules(twoDimRulesArray, defaultFunction)
        return {
			version: thiz.version,
			privRules: rules,
            evaluate: function(inputs){
                var logDebug = isNotUndef(console) && thiz.debug
                if (isUndef(inputs) || inputs == null){
                    if (isNotUndef(console)){ console.error('-> Cannot evaluate null or undefined input!') }
                    return
                }
                if (logDebug){ console.log('-> Evaluating inputs= '+thiz.objectToString(inputs)+' ---') }
                var returnResponse = null
				var ruleMatched = false
                for (var i = 0; i < rules.length; i++){
                    var rule = rules[i]
                    if (thiz.equals(rule.condition, inputs)){    // if we have a match
						ruleMatched = true
                        if (isNotUndef(rule.then)){
                            if (isNotUndef(console)){ console.log('-> Matched rule #' + i + ' - inputs=' + thiz.objectToString(inputs)) }
                            var response = rule.then(rule.params, inputs)
                            if (logDebug){ console.log('-> Found rule match: ' + rule.toString() + ' output:' + response) }
                            returnResponse = response
                        } else {
                            if (logDebug){ console.log('Cannot execute undefined function!') }
                        }
                        if (!multiMode){
                            break
                        }
                    }
                }
                if (!ruleMatched){
                    if (isNotUndef(console)){ console.log('-> No rule matched - inputs=' + thiz.objectToString(inputs)) }
                }
                return returnResponse
            }
        };
    },

    buildRules: function(twoDimensionalArray, defaultFunction){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        var rules = []
        if (isUndef(twoDimensionalArray) || twoDimensionalArray == null || isUndef(twoDimensionalArray.length) || twoDimensionalArray.length == 0){
            throw 'twoDimensionalArray must be a two dimensional array!'
        }
        var headers = twoDimensionalArray[0] // first row is the list of fields
        var nonStrings = headers.filter(function(a){ return typeof a !== 'string' })
        if (nonStrings.length > 0){
            var mesg = 'Header row must only contain strings! headers=' + headers.join(',')
            if (logDebug){ console.error(mesg) }
            throw mesg
        }
        var fieldNames = headers.filter(function(a){ return a.indexOf && a.indexOf('p_') === -1 })
        var inputNames = headers.map(function(a){ if (a.indexOf && a.indexOf('p_') === 0){ return a.substring(2) } }).filter(isNotUndef) // this performance could be improved
		if (fieldNames.length == 0){
			throw 'Could not find any field names! headerRow=' + headers.join(',')
		}
        var expectedLength = fieldNames.length + inputNames.length
        for (var i = 1; i < twoDimensionalArray.length; i++){
            var row = twoDimensionalArray[i]
            if (isNotUndef(row)){
                var actualLen = row.length
                if (actualLen != expectedLength && actualLen != expectedLength+1){
                    throw 'Expected length='+expectedLength+' but found length='+row.length+'! row#='+i
                }
                var rule = this.buildRule(fieldNames, inputNames, row, defaultFunction)
                if (logDebug){ console.log(rule.toString()) }
                if (isNotUndef(rule) && rule !== null){
                    rules[rules.length] = rule
                }
            } else {
                if (logDebug){ console.log('Rejecting undefined row: #' + i + ' headers=' + fieldNames.join(',')) }
            }
        }
        return rules
    },

    buildRule: function(fieldNames, inputNames, conditions, defaultCondition){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        if (isUndef(defaultCondition)){
            if (logDebug){ console.log('-> Found undefined defaultFunction, replacing with placeholder...') }
            defaultCondition = function(){}
        }
        if (isNotUndef(conditions)){
            // build condition and input objects
            var fnlen = fieldNames.length
            var inlen = inputNames.length
            var conditionObject = {}, inputObject = {}
            fieldNames.forEach(function(fld, i){ conditionObject[fld] = conditions[i] })
            inputNames.forEach(function(fld, i){ inputObject[fld] = conditions[i+fnlen] })
            // determine then function
            var thenCondition
            var overrideCondition = conditions[conditions.length-1]
            if ((fnlen + inlen) < conditions.length && isFn(overrideCondition)){
                thenCondition = overrideCondition
				if (logDebug){ console.log("-> Using override function.") }
            } else {
                thenCondition = defaultCondition
				if (logDebug){ console.log("-> Using default function - check1=" + thiz.objectToString(conditions)) }
			}
            return {condition: conditionObject, params: inputObject, then: thenCondition, toString: function(){
                return "Rule={ conditions: " + thiz.objectToString(conditionObject) + ", params: " + thiz.objectToString(inputObject) + ", then: "+ typeof thenCondition+" }"
            }}
        }
    },

    /* -- N.B. These below methods should be in a common library. -- */

    objectToString: function(object){
        return "("+Object.keys(object).map(
            function(key){ return key+"="+(isFn(object[key]) ? 'function' : object[key]); }
            ).join(",")+")"
    },

    /* does a deep match, of 1st level fields, of an object */
    equals: function(a, b){
        var thiz = this, logDebug = isNotUndef(console) && thiz.debug
        var keys = Object.keys(a)
        if (logDebug){ console.log('-> Checking keys: ' + keys.join(',')) }
        for (var i = 0; i < keys.length; i++){
            var field = keys[i]
            // check if a is null or undefined, if so, compare using ===
            var aIsNull = isUndef(a[field]) || a[field] === null
            // check if the objects match
            var matches = (aIsNull ? a[field] === b[field] : a[field] == b[field])
                || (isFn(a[field]) && a[field](b[field]))   // function
                || (a[field] instanceof Array && b[field] instanceof Array && a[field].compare(b[field]).equal)   // array
            if (!matches){
                if (logDebug){ console.log('-> Rejecting match on field [rule, actual] -> ' + field + ' [' + a[field] + ' != ' + b[field] + ']') }
                return false;
            } else {
                if (logDebug){ console.log('-> Found match on field [rule, actual] -> ' + field + ' [' + a[field] + ' == ' + b[field] + ']') }
            }
        }
        // everything matches, return true!
        return true;
    }

};
var _re = RulesEngine
<|endoftext|>


<commit_before>import ClassNames from "classnames";
import HeaderBar from "./HeaderBar.js";
import ProjectAnimationContainer from "./ProjectAnimationContainer.js";
import ProjectDetailsIntroView from "./ProjectDetailsIntroView.js";
import ProjectDetailsMainView from "./ProjectDetailsMainView.js";
import ProjectList from "./ProjectList.js";
import PropTypes from "prop-types";
import React from "react";
import ReactDOM from "react-dom";

/**
 * PortfolioContainer is the main component which handles the swipe events to move projects up and down
 * and the left, right click events to change the selected image within a project
 */
class PortfolioContainer extends React.Component{
  constructor() {
    super();
    this.isAnimating = false;
    this.currentProjectIndex = PortfolioContainer.unSetProjectIndex;
    this.animationDirection = ProjectAnimationContainer.ANIMATING.UP;
    this.animationDuration = 1200;
    this.state = {
      showContactModal: false,
      showListView: true,
      currentProject : undefined,
      showIsAnimating : false,
      items : []
    };
  }
  selctProject = (projectName) => {
    if (this.state.currentProject === undefined || this.state.currentProject.name != projectName) {
      this.updateCurrentProject(projectName);
    }
    else {
      this.handleProjectDetailsShow();
    }
  }
  updateCurrentProject = (projectName) => {
    if (this.isAnimating === true) return;
    if (this.state.showListView === false) return;
    let currentProject;

    this.setAnimating();
    for (let i = 0; i < this.props.projects.length; i++) {
      if (this.props.projects[i].name != projectName) {
        this.props.projects[i].active = false;
        continue;
      }
      if (i < this.currentProjectIndex) {
        this.animationDirection = ProjectAnimationContainer.ANIMATING.DOWN;
      } else {
        this.animationDirection = ProjectAnimationContainer.ANIMATING.UP;
      }
      this.setState({"currentProject" : this.props.projects[i]});
      this.currentProjectIndex = i;
      this.props.projects[i].active = true;
      currentProject = this.props.projects[i];
    }

    if (currentProject !== undefined) {
      this.setState({"animatedProject" : currentProject});
      this.setState({"animatedImageUrl" : currentProject.images[0]});
      this.setState({"animatedImageUrlIndex" : 0});
    }
    else {
      // no project means reset
      this.animationDirection = ProjectAnimationContainer.ANIMATING.DOWN;
      this.currentProjectIndex = PortfolioContainer.unSetProjectIndex;
      this.setState({"animatedProject" : null});
      this.setState({"animatedImageUrl" : null});
      this.setState({"animatedImageUrlIndex" : null});
    }
    this.setNotAnimating();
  }
  handleProjectDetailsShow = () => {
    this.setAnimating();
    this.setState({"showListView" : false});
    this.setNotAnimating();
  }
  handleProjectListShow = () => {
    if (this.props.projects == undefined) return;
    this.isAnimating = false;
    this.setState({"showListView" : true});
  }
  hideContactView = () => {
    this.setState({"showContactModal" : false});
  }
  showContactView = () => {
    this.setState({"showContactModal" : true});
  }
  componentDidMount = () => {
    const elem = ReactDOM.findDOMNode(this);
    elem.addEventListener("wheel", this.handleWheel);
    elem.addEventListener("touchmove", this.handleSwipe);
    elem.addEventListener("touchstart", this.handleSwipeStart);
  }
  handleWheel = (event) => {
    if (this.state.showListView == true) {
      event.preventDefault();
    }
    if (this.isAnimating !== false) return;

    if (event.deltaY < 0) (this.moveDown());
    if (event.deltaY > 0) (this.moveUp());
  }
  handleSwipe = (event) => {
    if (this.state.showListView == true) {
      event.preventDefault();
    }
    if (this.isAnimating !== false) return;

    if (event.touches[0].screenY < this.startY) {
      this.moveUp();
    } else {
      this.moveDown();
    }

    this.setAnimating();
  }
  handleSwipeStart = (event) => {
    this.startY = event.touches[0].screenY;
  }
  chooseProjectOne = () => {
    this.moveUp();
  }
  moveUp = () => {
    if (this.props.projects == undefined) return;
    if (this.currentProjectIndex < (this.props.projects.length - 1)) {
      this.updateCurrentProject(this.props.projects[this.currentProjectIndex + 1].name);
    }
  }
  moveDown = () => {
    if (this.props.projects == undefined) return;
    if (this.currentProjectIndex > 0) {
      this.updateCurrentProject(this.props.projects[this.currentProjectIndex - 1].name);
    }

    if (this.currentProjectIndex == 0) {
      this.updateCurrentProject("");
    }
  }
  setAnimating = () => {
    this.isAnimating = true;
    this.setState({"showIsAnimating" : true});
  }
  setNotAnimating = () => {
    const self = this;
    this.timeout = setTimeout(() => {
      self.isAnimating = false;
      self.setState({"showIsAnimating" : false});
    }, this.animationDuration);
  }
  /**
   * Cycle left through images related to a project
   */
  clickLeftIndividualProjectCarousel = () => {
    if (this.isAnimating ===   true) return;
    this.setAnimating();

    this.animationDirection = ProjectAnimationContainer.ANIMATING.LEFT;
    let newIndex;

    if (this.state.animatedImageUrlIndex != 0) {
      newIndex = this.state.animatedImageUrlIndex - 1;
    } else {
      newIndex = this.state.currentProject.images.length - 1;
    }

    this.setState({"animatedImageUrl" : this.state.currentProject.images[newIndex]});
    this.setState({"animatedImageUrlIndex" : newIndex});

    this.setNotAnimating();
  }
  /**
   * Cycle right through images related to a project
   */
  clickRightIndividualProjectCarousel = () => {
    if (this.isAnimating ===   true) return;
    this.setAnimating();

    this.animationDirection = ProjectAnimationContainer.ANIMATING.RIGHT;
    let newIndex;

    if (this.state.animatedImageUrlIndex != this.state.currentProject.images.length - 1) {
      newIndex = this.state.animatedImageUrlIndex + 1;
    } else {
      newIndex =  0;
    }

    this.setState({"animatedImageUrl" : this.state.currentProject.images[newIndex]});
    this.setState({"animatedImageUrlIndex" : newIndex});

    this.setNotAnimating();
  }
  getRootStatusClasses = () => {
    let overallStatusClasses;
    if (this.props.imagesReady == false ){
      overallStatusClasses = ClassNames({"imageLoadingView_active": true});
    }
    else if (this.state.showContactModal == true) {
      overallStatusClasses = ClassNames({"modalView_active": true});
    }
    else if (this.state.showListView == true && this.currentProjectIndex == PortfolioContainer.unSetProjectIndex) {
      overallStatusClasses = ClassNames({"intialView_active": true});
    }
    else if (this.state.showListView == true && this.currentProjectIndex != PortfolioContainer.unSetProjectIndex) {
      overallStatusClasses = ClassNames({"projectListView_active": true});
    }
    else {
      overallStatusClasses = ClassNames({
        "projectDetailsView_active": true,
        "singleImageProject" : this.state.currentProject.images.length == 1 ? true : false
      });
    }
    return overallStatusClasses;
  }
  render = () => {
    const animatingStatusClass = ClassNames({
      "animating_active" : this.state.showIsAnimating
    });
    /**
     *  if (this.props.imagesReady == false ){
        overallStatusClasses = "imageLoadingView_active";
    }
    else if (this.state.showListView == true && this.currentProjectIndex == -1) {
        overallStatusClasses = "intialView_active";
    }
    else if (this.state.showListView == true && this.currentProjectIndex != -1) {
        overallStatusClasses = "projectListView_active";
    }
    else if (this.state.currentProject.images.length == 1) {
      overallStatusClasses = "projectDetailsView_active singleImageProject"
    }
    else {
        overallStatusClasses = "projectDetailsView_active"
    }

    if (this.state.showContactModal == true) {
        overallStatusClasses += " modalView_active";
    }
     */

    return (

        <HeaderBar
          hideContactView={this.hideContactView}
          showContactView={this.showContactView}
          handleProjectListShow={this.handleProjectListShow}
        ></HeaderBar>

        <ProjectDetailsIntroView currentProject={this.state.currentProject}></ProjectDetailsIntroView>

          <ProjectAnimationContainer
            animationDirection={this.animationDirection}
            animationDuration={this.animationDuration}
            animatedImageUrl={this.state.animatedImageUrl}>
          </ProjectAnimationContainer>

            <ProjectList
              projects={this.props.projects}
              selctProject={this.selctProject}
              handleProjectDetailsShow={this.handleProjectDetailsShow}
              chooseProjectOne={this.chooseProjectOne}
              imagesReady={this.props.imagesReady}
              currentProjectIndex={this.currentProjectIndex}>
            </ProjectList>

          <ProjectDetailsMainView
            currentProject={this.state.currentProject}
            handleProjectListShow={this.handleProjectListShow} >
          </ProjectDetailsMainView>

    );
  }
}
PortfolioContainer.unSetProjectIndex = -1;
PortfolioContainer.propTypes = {
  projects: PropTypes.arrayOf(PropTypes.object),
  imagesReady: PropTypes.bool,
};

export default PortfolioContainer;
<commit_msg>make animatedImageUrlIndex a local class field<commit_after>import ClassNames from "classnames";
import HeaderBar from "./HeaderBar.js";
import ProjectAnimationContainer from "./ProjectAnimationContainer.js";
import ProjectDetailsIntroView from "./ProjectDetailsIntroView.js";
import ProjectDetailsMainView from "./ProjectDetailsMainView.js";
import ProjectList from "./ProjectList.js";
import PropTypes from "prop-types";
import React from "react";
import ReactDOM from "react-dom";

/**
 * PortfolioContainer is the main component which handles the swipe events to move projects up and down
 * and the left, right click events to change the selected image within a project
 */
class PortfolioContainer extends React.Component{
  constructor() {
    super();
    this.isAnimating = false;
    this.currentProjectIndex = PortfolioContainer.unSetProjectIndex;
    this.animationDirection = ProjectAnimationContainer.ANIMATING.UP;
    this.animationDuration = 1200;
    this.state = {
      showContactModal: false,
      showListView: true,
      currentProject : undefined,
      showIsAnimating : false,
      items : []
    };
  }
  selctProject = (projectName) => {
    if (this.state.currentProject === undefined || this.state.currentProject.name != projectName) {
      this.updateCurrentProject(projectName);
    }
    else {
      this.handleProjectDetailsShow();
    }
  }
  updateCurrentProject = (projectName) => {
    if (this.isAnimating === true) return;
    if (this.state.showListView === false) return;
    let currentProject;

    this.setAnimating();
    for (let i = 0; i < this.props.projects.length; i++) {
      if (this.props.projects[i].name != projectName) {
        this.props.projects[i].active = false;
        continue;
      }
      if (i < this.currentProjectIndex) {
        this.animationDirection = ProjectAnimationContainer.ANIMATING.DOWN;
      } else {
        this.animationDirection = ProjectAnimationContainer.ANIMATING.UP;
      }
      this.setState({"currentProject" : this.props.projects[i]});
      this.currentProjectIndex = i;
      this.props.projects[i].active = true;
      currentProject = this.props.projects[i];
    }

    if (currentProject !== undefined) {
      this.setState({"animatedImageUrl" : currentProject.images[0]});
      this.animatedImageIndex = 0;
    }
    else {
      // no project means reset
      this.animationDirection = ProjectAnimationContainer.ANIMATING.DOWN;
      this.currentProjectIndex = PortfolioContainer.unSetProjectIndex;
      this.setState({"animatedImageUrl" : null});
      this.animatedImageIndex = null;
    }
    this.setNotAnimating();
  }
  handleProjectDetailsShow = () => {
    this.setAnimating();
    this.setState({"showListView" : false});
    this.setNotAnimating();
  }
  handleProjectListShow = () => {
    if (this.props.projects == undefined) return;
    this.isAnimating = false;
    this.setState({"showListView" : true});
  }
  hideContactView = () => {
    this.setState({"showContactModal" : false});
  }
  showContactView = () => {
    this.setState({"showContactModal" : true});
  }
  componentDidMount = () => {
    const elem = ReactDOM.findDOMNode(this);
    elem.addEventListener("wheel", this.handleWheel);
    elem.addEventListener("touchmove", this.handleSwipe);
    elem.addEventListener("touchstart", this.handleSwipeStart);
  }
  handleWheel = (event) => {
    if (this.state.showListView == true) {
      event.preventDefault();
    }
    if (this.isAnimating !== false) return;

    if (event.deltaY < 0) (this.moveDown());
    if (event.deltaY > 0) (this.moveUp());
  }
  handleSwipe = (event) => {
    if (this.state.showListView == true) {
      event.preventDefault();
    }
    if (this.isAnimating !== false) return;

    if (event.touches[0].screenY < this.startY) {
      this.moveUp();
    } else {
      this.moveDown();
    }
    this.setAnimating();
  }
  handleSwipeStart = (event) => {
    this.startY = event.touches[0].screenY;
  }
  chooseProjectOne = () => {
    this.moveUp();
  }
  moveUp = () => {
    if (this.props.projects == undefined) return;
    if (this.currentProjectIndex < (this.props.projects.length - 1)) {
      this.updateCurrentProject(this.props.projects[this.currentProjectIndex + 1].name);
    }
  }
  moveDown = () => {
    if (this.props.projects == undefined) return;
    if (this.currentProjectIndex > 0) {
      this.updateCurrentProject(this.props.projects[this.currentProjectIndex - 1].name);
    }

    if (this.currentProjectIndex == 0) {
      this.updateCurrentProject("");
    }
  }
  setAnimating = () => {
    this.isAnimating = true;
    this.setState({"showIsAnimating" : true});
  }
  setNotAnimating = () => {
    const self = this;
    this.timeout = setTimeout(() => {
      self.isAnimating = false;
      self.setState({"showIsAnimating" : false});
    }, this.animationDuration);
  }
  /**
   * Cycle left through images related to a project
   */
  clickLeftIndividualProjectCarousel = () => {
    if (this.isAnimating ===   true) return;
    this.setAnimating();

    this.animationDirection = ProjectAnimationContainer.ANIMATING.LEFT;

    if (this.animatedImageIndex != 0) {
      this.animatedImageIndex = this.animatedImageIndex - 1;
    } else {
      this.animatedImageIndex = this.state.currentProject.images.length - 1;
    }
    this.setState({"animatedImageUrl" : this.state.currentProject.images[this.animatedImageIndex]});
    this.setNotAnimating();
  }
  /**
   * Cycle right through images related to a project
   */
  clickRightIndividualProjectCarousel = () => {
    if (this.isAnimating ===   true) return;
    this.setAnimating();

    this.animationDirection = ProjectAnimationContainer.ANIMATING.RIGHT;

    if (this.animatedImageIndex != this.state.currentProject.images.length - 1) {
      this.animatedImageIndex = this.animatedImageIndex + 1;
    } else {
      this.animatedImageIndex =  0;
    }
    this.setState({"animatedImageUrl" : this.state.currentProject.images[this.animatedImageIndex]});
    this.setNotAnimating();
  }
  getRootStatusClasses = () => {
    let overallStatusClasses;
    if (this.props.imagesReady == false ){
      overallStatusClasses = ClassNames({"imageLoadingView_active": true});
    }
    else if (this.state.showContactModal == true) {
      overallStatusClasses = ClassNames({"modalView_active": true});
    }
    else if (this.state.showListView == true && this.currentProjectIndex == PortfolioContainer.unSetProjectIndex) {
      overallStatusClasses = ClassNames({"intialView_active": true});
    }
    else if (this.state.showListView == true && this.currentProjectIndex != PortfolioContainer.unSetProjectIndex) {
      overallStatusClasses = ClassNames({"projectListView_active": true});
    }
    else {
      overallStatusClasses = ClassNames({
        "projectDetailsView_active": true,
        "singleImageProject" : this.state.currentProject.images.length == 1 ? true : false
      });
    }
    return overallStatusClasses;
  }
  render = () => {
    const animatingStatusClass = ClassNames({
      "animating_active" : this.state.showIsAnimating
    });
    /**
     *  if (this.props.imagesReady == false ){
        overallStatusClasses = "imageLoadingView_active";
    }
    else if (this.state.showListView == true && this.currentProjectIndex == -1) {
        overallStatusClasses = "intialView_active";
    }
    else if (this.state.showListView == true && this.currentProjectIndex != -1) {
        overallStatusClasses = "projectListView_active";
    }
    else if (this.state.currentProject.images.length == 1) {
      overallStatusClasses = "projectDetailsView_active singleImageProject"
    }
    else {
        overallStatusClasses = "projectDetailsView_active"
    }

    if (this.state.showContactModal == true) {
        overallStatusClasses += " modalView_active";
    }
     */

    return (

        <HeaderBar
          hideContactView={this.hideContactView}
          showContactView={this.showContactView}
          handleProjectListShow={this.handleProjectListShow}
        ></HeaderBar>

        <ProjectDetailsIntroView currentProject={this.state.currentProject}></ProjectDetailsIntroView>

          <ProjectAnimationContainer
            animationDirection={this.animationDirection}
            animationDuration={this.animationDuration}
            animatedImageUrl={this.state.animatedImageUrl}>
          </ProjectAnimationContainer>

            <ProjectList
              projects={this.props.projects}
              selctProject={this.selctProject}
              handleProjectDetailsShow={this.handleProjectDetailsShow}
              chooseProjectOne={this.chooseProjectOne}
              imagesReady={this.props.imagesReady}
              currentProjectIndex={this.currentProjectIndex}>
            </ProjectList>

          <ProjectDetailsMainView
            currentProject={this.state.currentProject}
            handleProjectListShow={this.handleProjectListShow} >
          </ProjectDetailsMainView>

    );
  }
}
PortfolioContainer.unSetProjectIndex = -1;
PortfolioContainer.propTypes = {
  projects: PropTypes.arrayOf(PropTypes.object),
  imagesReady: PropTypes.bool,
};

export default PortfolioContainer;
<|endoftext|>


<commit_before>"use strict";
var app_component_1 = require('./app.component');
var testing_1 = require('@angular/core/testing');
var platform_browser_1 = require('@angular/platform-browser');
describe('AppComponent', function () {
    var de;
    var comp;
    var fixture;
    beforeEach(testing_1.async(function () {
        testing_1.TestBed.configureTestingModule({
            declarations: [app_component_1.AppComponent]
        })
            .compileComponents();
    }));
    beforeEach(function () {
        fixture = testing_1.TestBed.createComponent(app_component_1.AppComponent);
        comp = fixture.componentInstance;
        de = fixture.debugElement.query(platform_browser_1.By.css('h1'));
    });
    it('should create component', function () { return expect(comp).toBeDefined(); });
    it('should have expected <h1> text', function () {
        fixture.detectChanges();
        var h1 = de.nativeElement;
        expect(h1.innerText).toMatch(/angular/i, '<h1> should say something about "Angular"');
    });
});
//# sourceMappingURL=app.component.spec.js.map<commit_msg>Delete app.component.spec.js<commit_after><|endoftext|>


<commit_before>/* globals Event */
'use strict';

app.directive('wizard', [
  'pageService',
  function (page) {
    return {
      restrict: 'E',
      templateUrl: 'site/wizard.html',
      transclude: true,
      controller: ['$scope', '$attrs', function ($scope, $attrs) {
        this.name = $scope.wizardName = $attrs.name;
        $scope.steps = [];
        $scope.step = {};
        $scope.activeStep = undefined;

        var target = page.$location.search().page;
        this.addStep = function (step) {
          $scope.steps.push(step);
          $scope.step[step.name] = step;

          $scope.registerStep(step);

          if (!$scope.activeStep || target === step.name)
            $scope.activateStep(step);
        };

        $scope.activateStep = function (newStep) {
          if (!newStep.allow || $scope.activeStep === newStep)
            return;

          if ($scope.switchStep && !$scope.switchStep(newStep))
            return;

          if ($scope.activeStep)
            $scope.activeStep.active = false;
          page.$location.replace().search('page', newStep.name);
          $scope.activeStep = newStep;
          newStep.active = true;
          page.$timeout(function() {
            window.dispatchEvent(new Event('scroll'));
          });
        };
      }],
    };
  }
]);
<commit_msg>Clear tooltips and messages on wizard tab switch<commit_after>/* globals Event */
'use strict';

app.directive('wizard', [
  '$location', '$timeout', 'tooltipService', 'messageService',
  function ($location, $timeout, tooltips, messages) {
    return {
      restrict: 'E',
      templateUrl: 'site/wizard.html',
      transclude: true,
      controller: ['$scope', '$attrs', function ($scope, $attrs) {
        this.name = $scope.wizardName = $attrs.name;
        $scope.steps = [];
        $scope.step = {};
        $scope.activeStep = undefined;

        var target = $location.search().page;
        this.addStep = function (step) {
          $scope.steps.push(step);
          $scope.step[step.name] = step;

          $scope.registerStep(step);

          if (!$scope.activeStep || target === step.name)
            $scope.activateStep(step);
        };

        $scope.activateStep = function (newStep) {
          if (!newStep.allow || $scope.activeStep === newStep)
            return;

          if ($scope.switchStep && !$scope.switchStep(newStep))
            return;

          if ($scope.activeStep)
            $scope.activeStep.active = false;
          $location.replace().search('page', newStep.name);
          $scope.activeStep = newStep;
          newStep.active = true;
          tooltips.clear();
          messages.clear();
          $timeout(function() {
            window.dispatchEvent(new Event('scroll'));
          });
        };
      }],
    };
  }
]);
<|endoftext|>


<commit_before>'use strict';

angular.module('myApp.heatmap', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/heatmap', {
    templateUrl: 'views/heatmap.html',
    controller: 'heatmapCtrl'
  });
}])

.factory('heatmapDataService', ['$http', function($http){
	var _servObj = {};
	var _dataObj = {};

	var _setUrl = function(url){
		_url = url;

		return _servObj;
	}

	var _getUrl = function(){
		return _url;
	}

	//get data and format it as cal-heatmap expects
	//TODO - data obj does not need to be refereshed every time we want to get??? can we re-serve from memory if we dont believe anthing has changed???
	var _getData = function(){
		return $http.get('http://10.239.3.132:8080/dailyhistory/544876bf2e905908b6e5f595').
		success(function(data){
				var i = 0;
				var historyArray = data.his;

				console.log('success????');

				for(i = 0; i < historyArray.length; i++){
					_dataObj[(historyArray[i].timestamp / 1000)+""] = +historyArray[i].value;
				}

			}).
		error(function(data){
			console.log('error???');
			throw('there was problem getting data');
		});
	}

	_servObj = {
		getUrl : _getUrl,
		setUrl: _setUrl,
		getData : _getData,
		dataObj : _dataObj
	}

	return _servObj;

}])

.factory('persistHeatmapService', ['persistViewService', function(persistViewService){
	var _servObj = {};

	//inherit functionality through composition
	angular.extend(_servObj, persistViewService);

	_servObj.setViewType('heatmap');

	_servObj.printThis = function(){
		console.log(this);
			console.log(this.getTimestamp());
	}

	return _servObj;
}])

.controller('heatmapCtrl', ['$scope', '$location', '$route', 'zoomHeatmapService', 'persistHeatmapService', 'heatmapDataService', function($scope, $location, $route, zoomHeatmapService, persistHeatmapService, heatmapDataService) {
	var vm = this;

	//control whether the view needs to be reloaded.
	vm.rendered = false;

	//inject the zoomHeatmapService into the scope.
	angular.extend(vm, zoomHeatmapService);
	angular.extend(vm, persistHeatmapService);

	//for testing multiple controllers inheriting the same service singleton
	vm.change = function(){
		var max = 30;
		var min = 10;
		vm.heatmapConfig.range = Math.floor(Math.random()*(max-min+1)+min);
		vm.rendered = false;

		console.log('before:');
		console.log(vm);
		console.log(vm.getTimestamp());

		vm.setTimestamp(new Date());

		console.log('after:');
		console.log(vm.getTimestamp());

		$scope.myTimestamp = vm.getTimestamp();

		//In one second, reload the heatmap component with the changed configuration.
		window.setTimeout(function(){
			vm.rendered = true;
			$scope.$apply();
		}, 1000);
	}

	heatmapDataService.getData().then(function (dataddd){
		vm.heatmapConfig = {
			onClick:function(date, value){
				zoomHeatmapService.setTimestamp(date.getTime());

				$location.url('/zoomHeatmap');
				$route.reload();
			},
			domain: 'day',
			data: heatmapDataService.dataObj,
			subDomain: 'hour',
			range: 30,	//number of domains (days in current implementation)
			cellSize: 20, //px size of cells
			cellPadding: 1,	//px between cells
			cellRadius: 2,	//px of cell radius
			domainGutter: 0, //px padding between dates
			colLimit: 1, //number of colums per domain
			legend: [0,1,2,3,4,5,6,7,8,9,10,11,12],	//legend. Remember its like actually the count
													//TODO: make vm change dependant on dataset
			legendVerticalPosition: "center",
			legendHorizontalPosition: "right",
			legendOrientation: "vertical",
			legendMargin: [10, 10, 10, 10],
			legendColors: ['#00FF00', '#FF0000'],	//colors of legend gradient
			subDomainTextFormat: '%H', /*function(date, value) {
				if (date.getMinutes() === 0) {
					return date.getHours();
				}
				else return date.getMinutes();
			},*/
			start : new Date(1413864000000),
			domainLabelFormat: function(date) {//format of each domain label. "x axis" labels
				var month =
					["Jan", "Feb", "Mar", "Apr",
					 "May", "Jun", "Jul", "Aug",
					 "Sep", "Oct", "Nov", "Dec"];
				if (date.getDate() % 2 === 0) {
					return date.getDate();
				}
				else {
					return month[date.getMonth()];
				}
			},
			label: {
				width: 30,
				position: 'bottom'
				//rotate: 'left' doesn't work if position is bottom!
			}
		};

		vm.rendered = true;
	});

}]);<commit_msg>random comment<commit_after>'use strict';

angular.module('myApp.heatmap', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/heatmap', {
    templateUrl: 'views/heatmap.html',
    controller: 'heatmapCtrl'
  });
}])

.factory('heatmapDataService', ['$http', function($http){
	var _servObj = {};
	var _dataObj = {};

	var _setUrl = function(url){
		_url = url;

		return _servObj;
	}

	var _getUrl = function(){
		return _url;
	}

	//get data and format it as cal-heatmap expects
	//TODO - data obj does not need to be refereshed every time we want to get??? can we re-serve from memory if we dont believe anthing has changed???
	var _getData = function(){
		return $http.get('http://10.239.3.132:8080/dailyhistory/544876bf2e905908b6e5f595').
		success(function(data){
				var i = 0;
				var historyArray = data.his;

				console.log('success????');

				for(i = 0; i < historyArray.length; i++){
					_dataObj[(historyArray[i].timestamp / 1000)+""] = +historyArray[i].value;
				}

			}).
		error(function(data){
			console.log('error???');
			throw('there was problem getting data');
		});
	}

	_servObj = {
		getUrl : _getUrl,
		setUrl: _setUrl,
		getData : _getData,
		dataObj : _dataObj
	}

	return _servObj;

}])

.factory('persistHeatmapService', ['persistViewService', function(persistViewService){
	var _servObj = {};

	//inherit functionality through composition
	angular.extend(_servObj, persistViewService);

	_servObj.setViewType('heatmap');

	_servObj.printThis = function(){
		console.log(this);
			console.log(this.getTimestamp());
	}

	return _servObj;
}])

.controller('heatmapCtrl', ['$scope', '$location', '$route', 'zoomHeatmapService', 'persistHeatmapService', 'heatmapDataService', function($scope, $location, $route, zoomHeatmapService, persistHeatmapService, heatmapDataService) {
	var vm = this;

	//control whether the view needs to be reloaded.
	vm.rendered = false;

	//inject the zoomHeatmapService into the scope.
	angular.extend(vm, zoomHeatmapService);
	angular.extend(vm, persistHeatmapService);

	//for testing multiple controllers inheriting the same service singleton
	vm.change = function(){
		var max = 30;
		var min = 10;
		vm.heatmapConfig.range = Math.floor(Math.random()*(max-min+1)+min);
		vm.rendered = false;

		console.log('before:');
		console.log(vm);
		console.log(vm.getTimestamp());

		vm.setTimestamp(new Date());

		console.log('after:');
		console.log(vm.getTimestamp());

		$scope.myTimestamp = vm.getTimestamp();

		//In one second, reload the heatmap component with the changed configuration.
		window.setTimeout(function(){
			vm.rendered = true;
			$scope.$apply();
		}, 1000);
	}

	heatmapDataService.getData().then(function (dataddd){
		console.log(dataddd);

		vm.heatmapConfig = {
			onClick:function(date, value){
				zoomHeatmapService.setTimestamp(date.getTime());

				$location.url('/zoomHeatmap');
				$route.reload();
			},
			domain: 'day',
			data: heatmapDataService.dataObj,
			subDomain: 'hour',
			range: 30,	//number of domains (days in current implementation)
			cellSize: 20, //px size of cells
			cellPadding: 1,	//px between cells
			cellRadius: 2,	//px of cell radius
			domainGutter: 0, //px padding between dates
			colLimit: 1, //number of colums per domain
			legend: [0,1,2,3,4,5,6,7,8,9,10,11,12],	//legend. Remember its like actually the count
													//TODO: make vm change dependant on dataset
			legendVerticalPosition: "center",
			legendHorizontalPosition: "right",
			legendOrientation: "vertical",
			legendMargin: [10, 10, 10, 10],
			legendColors: ['#00FF00', '#FF0000'],	//colors of legend gradient
			subDomainTextFormat: '%H', /*function(date, value) {
				if (date.getMinutes() === 0) {
					return date.getHours();
				}
				else return date.getMinutes();
			},*/
			start : new Date(1413864000000),
			domainLabelFormat: function(date) {//format of each domain label. "x axis" labels
				var month =
					["Jan", "Feb", "Mar", "Apr",
					 "May", "Jun", "Jul", "Aug",
					 "Sep", "Oct", "Nov", "Dec"];
				if (date.getDate() % 2 === 0) {
					return date.getDate();
				}
				else {
					return month[date.getMonth()];
				}
			},
			label: {
				width: 30,
				position: 'bottom'
				//rotate: 'left' doesn't work if position is bottom!
			}
		};

		//after setting heatmap config, unhide the component.
		vm.rendered = true;
	});

}]);<|endoftext|>


<commit_before>import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { Link, Route, Switch } from 'react-router-dom';
import { css } from 'emotion';
import i18n, { i18nInit } from 'i18n';
import { ToastContainer } from 'react-toastify';
import Loadable from 'react-loadable';
import { DarkModeSwitch } from 'react-toggle-dark-mode';
import { Col, Container, Input, Row } from 'reactstrap';
import { auth, database } from 'services/firebase';
import { DARK, LIGHT, TRANSLATIONS } from 'consts';
import Discord from 'images/discord.png';
import { useLanguage } from 'selectors/language';
import { useTranslations } from 'selectors/translations';
import { useUserId } from 'selectors/userId';
import { useTranslationsImportTime } from 'selectors/translationsImportTime';
import useDarkMode from 'hooks/useDarkMode';

import SpinnerModal from 'components/SpinnerModal/SpinnerModal';
import SpyIcon from 'components/SpyIcon/SpyIcon';

const LoadableSettings = Loadable({ loader: () => import('../Settings/Settings'), loading: SpinnerModal });
const LoadableGame = Loadable({ loader: () => import('../Game/Game'), loading: SpinnerModal });
const LoadableJoinRoom = Loadable({ loader: () => import('../JoinRoom/JoinRoom'), loading: SpinnerModal });

export const App = () => {
  const [loading, setLoading] = useState(true);
  const [, setUserId] = useUserId();
  const [language, setLanguage] = useLanguage();
  const [translations, setTranslations] = useTranslations();
  const translationsImportTime = useTranslationsImportTime();
  const darkMode = useDarkMode();

  const changeLanguage = (newLanguage) => {
    i18n.changeLanguage(newLanguage);
    setLanguage(newLanguage);
  };

  const importTranslations = async () => {
    // imported less than 24 hours ago
    if (translationsImportTime && Date.now() - translationsImportTime < 24 * 60 * 60 * 1000) return null;

    const translationsSnapshot = await database.ref('translations').once('value');
    setTranslations(translationsSnapshot.val() || {});
  };

  useEffect(() => {
    i18nInit.then(() => {
      if (!language) {
        changeLanguage(i18n.language);
      }
      setLoading(false);
    });
    auth.signInAnonymously().then((authUser) => {
      setUserId(authUser.user.uid);
      importTranslations();
    });
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  if (loading) return null;

  return (
    <Container className={styles.container}>
      <Helmet
        defaultTitle="Spyfall"
      >
        <meta name="description" content="Free online spyfall game" />
      </Helmet>
      <Row className="justify-content-center">
        <Col xs={12} md={8} lg={6}>
          <Row className="align-items-center justify-content-center">
            <Col xs="8" sm="3" className={`${styles.topItems} text-left`}>
              <Link to="/">
                <SpyIcon />
                Spyfall
              </Link>
            </Col>
            <Col xs="4" sm="2" className={`${styles.topItems} text-right`}>
              <DarkModeSwitch
                onChange={darkMode.toggle}
                checked={darkMode.value}
                sunColor={DARK}
                moonColor={LIGHT}
                size={24}
              />
            </Col>
            <Col xs="12" sm="7" className={`${styles.topItems} text-center`}>
              <Input type="select" name="languages" id="languages" value={language} onChange={(evt) => changeLanguage(evt.target.value)}>
                {TRANSLATIONS.map((translation) =>
                  <option key={translation.id} value={translation.id}>{translation.name} - {translations[translation.id] || 0}%</option>
                )}
              </Input>
            </Col>
          </Row>
          <Switch>
            <Route exact path="/settings" component={LoadableSettings} />
            <Route exact path="/join" component={LoadableJoinRoom} />
            <Route exact path="/join/:roomId" component={LoadableJoinRoom} />
            <Route exact path="/" component={LoadableGame} />
          </Switch>
          <Row className={styles.localizationContainer}>
            <Col className="text-center">

              Help us with translations!
            </Col>
          </Row>
          <Row className={styles.localizationContainer}>
            <Col className="text-center">

                Join Spyfall Discord server

            </Col>
          </Row>
          <Row className={`${styles.footer} text-secondary`}>
            <Col className="text-center">
              <Row>
                <Col>
                  Spyfall is designed by Alexandr Ushan and published by Hobby World
                </Col>
              </Row>
              <Row>
                <Col>
                  Spy icon created by Dan Hetteix (TheNounProject)
                </Col>
              </Row>
              <Row>
                <Col>
                  https://github.com/adrianocola/spyfall
                </Col>
              </Row>
            </Col>
          </Row>
        </Col>
      </Row>
      <ToastContainer />
    </Container>
  );
};

const styles = {
  container: css({
    paddingTop: 50,
  }),
  topItems: css({
    marginTop: 10,
  }),
  localizationContainer: css({
    marginTop: 40,
    fontSize: '0.8rem',
  }),
  localizationLink: css({
    marginLeft: 10,
  }),
  discordImage: css({
    height: 32,
  }),
  discordLink: css({
    marginLeft: 10,
  }),
  footer: css({
    marginTop: 20,
    marginBottom: 30,
    fontSize: '0.8rem',
  }),
};

export default App;
<commit_msg>Added event to track dark mode adoption<commit_after>import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { Link, Route, Switch } from 'react-router-dom';
import { css } from 'emotion';
import i18n, { i18nInit } from 'i18n';
import { ToastContainer } from 'react-toastify';
import Loadable from 'react-loadable';
import { DarkModeSwitch } from 'react-toggle-dark-mode';
import { Col, Container, Input, Row } from 'reactstrap';
import { auth, database } from 'services/firebase';
import { DARK, LIGHT, TRANSLATIONS } from 'consts';
import Discord from 'images/discord.png';
import { useLanguage } from 'selectors/language';
import { useTranslations } from 'selectors/translations';
import { useUserId } from 'selectors/userId';
import { useTranslationsImportTime } from 'selectors/translationsImportTime';
import useDarkMode from 'hooks/useDarkMode';
import { logEvent } from 'utils/analytics';

import SpinnerModal from 'components/SpinnerModal/SpinnerModal';
import SpyIcon from 'components/SpyIcon/SpyIcon';

const LoadableSettings = Loadable({ loader: () => import('../Settings/Settings'), loading: SpinnerModal });
const LoadableGame = Loadable({ loader: () => import('../Game/Game'), loading: SpinnerModal });
const LoadableJoinRoom = Loadable({ loader: () => import('../JoinRoom/JoinRoom'), loading: SpinnerModal });

export const App = () => {
  const [loading, setLoading] = useState(true);
  const [, setUserId] = useUserId();
  const [language, setLanguage] = useLanguage();
  const [translations, setTranslations] = useTranslations();
  const translationsImportTime = useTranslationsImportTime();
  const darkMode = useDarkMode();

  const changeLanguage = (newLanguage) => {
    i18n.changeLanguage(newLanguage);
    setLanguage(newLanguage);
  };

  const importTranslations = async () => {
    // imported less than 24 hours ago
    if (translationsImportTime && Date.now() - translationsImportTime < 24 * 60 * 60 * 1000) return null;

    const translationsSnapshot = await database.ref('translations').once('value');
    setTranslations(translationsSnapshot.val() || {});
  };

  useEffect(() => {
    i18nInit.then(() => {
      if (!language) {
        changeLanguage(i18n.language);
      }
      setLoading(false);
    });
    auth.signInAnonymously().then((authUser) => {
      setUserId(authUser.user.uid);
      importTranslations();
    });
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    logEvent('THEME', darkMode.value ? 'dark' : 'light');
  }, [darkMode.value]);

  if (loading) return null;

  return (
    <Container className={styles.container}>
      <Helmet
        defaultTitle="Spyfall"
      >
        <meta name="description" content="Free online spyfall game" />
      </Helmet>
      <Row className="justify-content-center">
        <Col xs={12} md={8} lg={6}>
          <Row className="align-items-center justify-content-center">
            <Col xs="8" sm="3" className={`${styles.topItems} text-left`}>
              <Link to="/">
                <SpyIcon />
                Spyfall
              </Link>
            </Col>
            <Col xs="4" sm="2" className={`${styles.topItems} text-right`}>
              <DarkModeSwitch
                onChange={darkMode.toggle}
                checked={darkMode.value}
                sunColor={DARK}
                moonColor={LIGHT}
                size={24}
              />
            </Col>
            <Col xs="12" sm="7" className={`${styles.topItems} text-center`}>
              <Input type="select" name="languages" id="languages" value={language} onChange={(evt) => changeLanguage(evt.target.value)}>
                {TRANSLATIONS.map((translation) =>
                  <option key={translation.id} value={translation.id}>{translation.name} - {translations[translation.id] || 0}%</option>
                )}
              </Input>
            </Col>
          </Row>
          <Switch>
            <Route exact path="/settings" component={LoadableSettings} />
            <Route exact path="/join" component={LoadableJoinRoom} />
            <Route exact path="/join/:roomId" component={LoadableJoinRoom} />
            <Route exact path="/" component={LoadableGame} />
          </Switch>
          <Row className={styles.localizationContainer}>
            <Col className="text-center">

              Help us with translations!
            </Col>
          </Row>
          <Row className={styles.localizationContainer}>
            <Col className="text-center">

                Join Spyfall Discord server

            </Col>
          </Row>
          <Row className={`${styles.footer} text-secondary`}>
            <Col className="text-center">
              <Row>
                <Col>
                  Spyfall is designed by Alexandr Ushan and published by Hobby World
                </Col>
              </Row>
              <Row>
                <Col>
                  Spy icon created by Dan Hetteix (TheNounProject)
                </Col>
              </Row>
              <Row>
                <Col>
                  https://github.com/adrianocola/spyfall
                </Col>
              </Row>
            </Col>
          </Row>
        </Col>
      </Row>
      <ToastContainer />
    </Container>
  );
};

const styles = {
  container: css({
    paddingTop: 50,
  }),
  topItems: css({
    marginTop: 10,
  }),
  localizationContainer: css({
    marginTop: 40,
    fontSize: '0.8rem',
  }),
  localizationLink: css({
    marginLeft: 10,
  }),
  discordImage: css({
    height: 32,
  }),
  discordLink: css({
    marginLeft: 10,
  }),
  footer: css({
    marginTop: 20,
    marginBottom: 30,
    fontSize: '0.8rem',
  }),
};

export default App;
<|endoftext|>