<commit_before>var request = require('request'),
    cachedRequest = require('cached-request')(request),
    JsonDB = require('node-json-db'),
    config = require('../config.json');

cachedRequest.setCacheDirectory('/tmp/cache');

var db = new JsonDB('database', true, true);

var WarcraftLogs = function () {};

const raids = {
    1000: 'Molten Core',
    1001: 'Onyxia\'s Lair',
    1002: 'Blackwing Lair',
    1003: 'Zul\'Gurub'
}

const convertMilliseconds = (miliseconds) => {
    let days, hours, minutes, seconds, total_hours, total_minutes, total_seconds;

    total_seconds = parseInt(Math.floor(miliseconds / 1000));
    total_minutes = parseInt(Math.floor(total_seconds / 60));
    total_hours = parseInt(Math.floor(total_minutes / 60));
    days = parseInt(Math.floor(total_hours / 24));

    seconds = parseInt(total_seconds % 60);
    minutes = parseInt(total_minutes % 60);
    hours = parseInt(total_hours % 24);

    var ret = '';

    if (days) ret += (days > 9 ? days : '0'+days)+':';
    ret += (hours > 9 ? hours : '0'+hours)+':';
    ret += (minutes > 9 ? minutes : '0'+minutes)+':';
    ret += (seconds > 9 ? seconds : '0'+seconds);

    return ret;
};

const doRequest = (url, cb) => {
    let headers = {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'User-Agent': 'https://github.com/HxxxxxS/DiscordBot'
    }

    let options = {
        url: url,
        port: 443,
        method: 'GET',
        ttl: 5 * 60 * 1000,
        headers: headers,
        timeout: 15000
    }

    console.log('requesting '+url);

    cachedRequest(options, (error, response, body) => {
        console.log(`request returned status ${response.statusCode}`);
        if (!error && response.statusCode == 200) {
            let data = JSON.parse(body);
            cb(data, url);
        }else{
            console.log((error?error:response));
            message.channel.send(`Error: ${response.statusCode}`);
        }
    });
}

const stripCode = (code) => {
    if(code.indexOf("http") > -1){
        if(code.indexOf("#") > -1) code = code.split("#")[0]; // Remove everything after #
        if(code.indexOf("?") > -1) code = code.split("?")[0]; // Remove everything after ?
        code = code.split("/")[code.split("/").length - 1];   // Remove everything before /
    }
    return code;
}

const getSpecificLog = (code, cb) => {
    console.log('getSpecificLog called!');
    let url = `https://classic.warcraftlogs.com/v1/report/fights/${stripCode(code)}?api_key=${config.warcraft_logs.apikey}`;
    doRequest(url, (data, url) => {
        cb(data, url);
    });
}

const getLatestLog = (guildId, cb) => {
    console.log('getLatestLog called!');
    let zone = guildId.split('|')[1];
    let guild = guildId.split('|')[0];
    let url = `https://classic.warcraftlogs.com/v1/reports/guild/${guild}?api_key=${config.warcraft_logs.apikey}`
    doRequest(url, (data, url) => {
        getSpecificLog(data[0].id, cb);
    });
}

const drawOutput = (data, code, cb) => {
    console.log('drawOutput called!');
    let link = `https://classic.warcraftlogs.com/reports/${code}`;
    let embed = {
        title: data.title,
        url: link,
        thumbnail:{ url: 'https://dmszsuqyoe6y6.cloudfront.net/img/warcraft/zones/zone-'+data.zone+'-small.jpg' },
    }
    embed.fields = [{
        name: '\u200b',
        value:  (data.title != raids[data.zone] ? 'Zone: **' + raids[data.zone] + "**\n" : '') +
                'Date: **' + new Date(data.start).toDateString() + "**\n" +
                'Duration: **' + convertMilliseconds(data.end - data.start) + "**\n"
    },{name:'\u200b',value:'Bosses:'}];
    var bosses = [];
    var timer = 0;
    for (var i = 0; i<data.fights.length; i++)
    {
        var fight = data.fights[i];
        if (fight.end_time < timer) break;
        if (fight.boss)
        {
            if (bosses.indexOf(fight.boss)>-1) continue;
            bosses.push(fight.boss);
            var enemy;
            for (var j = 0; j<data.enemies.length; j++)
            {
                enemy = data.enemies[j];
                if (fight.name == enemy.name) break;
                if (data.enemies[j].type != "Boss") enemy = undefined;
            }
            if (!enemy) continue;
            var name = (enemy.name.length > 21 ? enemy.name.split(' ')[0] : enemy.name);
            field = {name: name, value: '', inline: true}
            for (var j = 0; j<enemy.fights.length; j++)
            {
                var spec_fight = data.fights[enemy.fights[j].id-1];
                if (!spec_fight.boss) continue;
                if (field.value.length > 666)
                {
                    fields.push(field);
                    field = {name: name, value: '', inline: true}
                }
                var timeStart = convertMilliseconds(spec_fight.start_time),
                    timeEnd = convertMilliseconds(spec_fight.end_time);
                field.value += (spec_fight.kill ? "✅" : "❌") + " - ";
                field.value += "["+timeEnd;
                field.value += "]("+link+"#fight="+spec_fight.id+" '";
                field.value += enemy.name+" "+(spec_fight.kill?'kill':'wipe')+" from "+timeStart+" to "+timeEnd+" | Length: "+convertMilliseconds(spec_fight.end_time-spec_fight.start_time)+"') "+"\n";
                if (spec_fight.kill) break;
            }

            embed.fields.push(field);
        }
        if (fight.end_time > timer) timer = fight.end_time;
    }
    if (bosses.length % 3 != 0) embed.fields.push({name:"\u200b ",value:"\u200b ",inline:true});
    embed.fields.push({name:'See the full log:',value:link});
    cb.edit({embed: embed});
}

const getWorldbuffs = (code, start, cb) => {
    console.log('getWorldbuffs called!');
    let url = `https://classic.warcraftlogs.com/v1/report/events/${code}?type=combatantinfo&start=${start}&end=${start}&api_key=${config.warcraft_logs.apikey}`;
    doRequest(url, (data, url) => {
        cb(data.events, url);
    });
}

const drawWorldbuffs = (events, log, url, cb) => {
    console.log('drawWorldbuffs called!');
    console.log(`Checking ${log.friendlies.length} friendlies and ${events.length} events`);
    let code = stripCode(url);
    let link = `https://classic.warcraftlogs.com/reports/${code}`;
    let embed = {
        title: `Worldbuffs for ${log.title} ${new Date(log.start).toDateString()}:`,
        url: link,
        fields: []
    }

    let friendlies = [];

    for (var i = log.friendlies.length - 1; i >= 0; i--) {
        let player = log.friendlies[i];
        if (!player.hasOwnProperty('server')) continue;
        friendlies[player.id] = {name: player.name, type: player.type, icon: player.icon, buffs: []};
    }

    for (var i = events.length - 1; i >= 0; i--) {
        var event = events[i];
        if (event.type !== 'combatantinfo') continue;
        if (event.hasOwnProperty('auras')) {
            for (var j = event.auras.length - 1; j >= 0; j--) {
                var aura = event.auras[j];
                friendlies[event.sourceID].buffs.push({name:aura.name,id:aura.ability,icon:aura.icon});
            }
        }
    }

    const emojis = cb.client.emojis;

    var l = 0;

    var fields = [];

    for (var i = friendlies.length - 1; i >= 0; i--) {
        let player = friendlies[i];
        if(!player) continue;
        const icon = emojis.find(emoji => emoji.name === player.type.split('-')[0]);
        let field = {
            name: `${(icon?icon+' ':'')}${player.name}`,
            value: [],
            inline: true
        };
        for (var j = player.buffs.length - 1; j >= 0; j--) {
            let buff = player.buffs[j];
            if (!buff) continue;
            const emoji = emojis.find(emoji => emoji.name === buff.icon.split('.')[0]);
            field.value.push(emoji);
            //field.value.push(`[${emoji}](https://classicdb.ch/?spell=${buff.ability} '${buff.name}')`);
        }
        if(field.name && field.value.length){
            fields.push(field);
        }
    }

    fields.sort((a,b) => {
        return b.value.length - a.value.length;
    })

    for (var i = fields.length - 1; i >= 0; i--) {
        if (fields.length < 9) fields[i].inline = false;
        fields[i].value = fields[i].value.join('')+'\u200b';
        l+=fields[i].value.length+fields[i].name.length;
    }

    if (l > 5500) {
        let cutoff = Math.ceil(fields.length / 3);
        let fields2 = fields.splice(0, cutoff - (cutoff % 3));

        fields2.push({name:"Continued in next message ",value:"\u200b ",inline:false});
        fields.push({name:'See the full log:',value:link, inline:false});

        embed.fields = fields2;
        cb.edit({embed:embed});
        cb.channel.send({embed:{title:'\u200b',fields: fields}});
    } else {
        embed.fields = fields;
        cb.edit({embed: embed});
    }

}

WarcraftLogs.prototype.Message = function(message)
{
    switch (message.channel.type) {
        case 'text':
            var id = message.guild.id;
            break;
        case 'dm':
        case 'group':
            var id = message.channel.id;
            break;
    }

    if (!id) return false;
    var guildId = db.getData('/warcraftlogs/guilds/' + id);
    var zone = guildId.split('|')[1];
    var guild = guildId.split('|')[0];

    var msgArr = message.content.split(" ");

    var action = msgArr[1];
    var aIndex = message.content.indexOf(action);

    switch (action) {
        case 'set':
            if (msgArr.length < 5) {
                message.reply(`You sent too few parameters. Expected syntax: logs [set] [classic|retail region realm Guild Name]`)
                return false;
            }
            var zone = msgArr[4] + '/' + msgArr[3] + "|" + msgArr[2];
            var guildName = msgArr[5];
            for (var i = 6; i <= msgArr.length -1; i++) {
                guildName += " " + msgArr[i];
            }
            guildId = guildName + '/' + zone;
            try {
                getLatestLog(guildId);
                db.push('/warcraftlogs/guilds/' + id, guildId);
            } catch (error) {
                message.reply(`Something went wrong. Expected syntax: logs [set] [classic|retail region realm Guild Name]`)
            }
            break;
        case 'latest':
        case undefined:
            message.channel.send('Getting latest log...')
            .then((sent) => {
                getLatestLog(guildId, (data, url) => {
                    let code = stripCode(url);
                    console.log(code);
                    drawOutput(data, code, sent);
                });
            });
            message.delete();
            break;
        case 'worldbuffs':
            let code = stripCode(msgArr[2]);
            message.channel.send(`Getting worldbuffs for ${code}...`)
            .then((sent) => {
                getSpecificLog(code, (log, url) => {
                    let start = 0;
                    for (var i = log.fights.length - 1; i >= 0; i--) {
                        if (log.fights[i].boss) {
                            start = log.fights[i].start_time;
                        }
                    }
                    if (log.zone == 1002) start = 0;
                    getWorldbuffs(code, Math.max(start,0), (data, url) => {
                        drawWorldbuffs(data, log, url, sent);
                    });
                });
            });
            break;
        case 'fix':
            if (msgArr.length < 3) {
                message.reply(`You sent too few parameters. Expected syntax: logs fix msgID url`)
                return false;
            }
            var msgID = msgArr[2];
            var link = msgArr[3];
            message.channel.fetchMessage(msgID)
            .then((sent)=>{
                getSpecificLog(link, (data, url) => {
                    let code = stripCode(url);
                    drawOutput(data, code, sent);
                })
            })
            message.delete();
            break;
        default:
            message.channel.send('Getting specified log...')
            .then((sent) => {
                getSpecificLog(msgArr[1], (data, url) => {
                    let code = stripCode(url);
                    drawOutput(data, code, sent);
                });
            });
            message.delete();
            break;

    }
}

module.exports = WarcraftLogs;
<commit_msg>Made wbuff command output cutoff dynamic for longer messages. Changed so emoji names can be the buff name instead of the icon name. Added ? for if emoji is missing.<commit_after>var request = require('request'),
    cachedRequest = require('cached-request')(request),
    JsonDB = require('node-json-db'),
    config = require('../config.json');

cachedRequest.setCacheDirectory('/tmp/cache');

var db = new JsonDB('database', true, true);

var WarcraftLogs = function () {};

const raids = {
    1000: 'Molten Core',
    1001: 'Onyxia\'s Lair',
    1002: 'Blackwing Lair',
    1003: 'Zul\'Gurub'
}

const convertMilliseconds = (miliseconds) => {
    let days, hours, minutes, seconds, total_hours, total_minutes, total_seconds;

    total_seconds = parseInt(Math.floor(miliseconds / 1000));
    total_minutes = parseInt(Math.floor(total_seconds / 60));
    total_hours = parseInt(Math.floor(total_minutes / 60));
    days = parseInt(Math.floor(total_hours / 24));

    seconds = parseInt(total_seconds % 60);
    minutes = parseInt(total_minutes % 60);
    hours = parseInt(total_hours % 24);

    var ret = '';

    if (days) ret += (days > 9 ? days : '0'+days)+':';
    ret += (hours > 9 ? hours : '0'+hours)+':';
    ret += (minutes > 9 ? minutes : '0'+minutes)+':';
    ret += (seconds > 9 ? seconds : '0'+seconds);

    return ret;
};

const doRequest = (url, cb) => {
    let headers = {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'User-Agent': 'https://github.com/HxxxxxS/DiscordBot'
    }

    let options = {
        url: url,
        port: 443,
        method: 'GET',
        ttl: 5 * 60 * 1000,
        headers: headers,
        timeout: 15000
    }

    console.log('requesting '+url);

    cachedRequest(options, (error, response, body) => {
        console.log(`request returned status ${response.statusCode}`);
        if (!error && response.statusCode == 200) {
            let data = JSON.parse(body);
            cb(data, url);
        }else{
            console.log((error?error:response));
            message.channel.send(`Error: ${response.statusCode}`);
        }
    });
}

const stripCode = (code) => {
    if(code.indexOf("http") > -1){
        if(code.indexOf("#") > -1) code = code.split("#")[0]; // Remove everything after #
        if(code.indexOf("?") > -1) code = code.split("?")[0]; // Remove everything after ?
        code = code.split("/")[code.split("/").length - 1];   // Remove everything before /
    }
    return code;
}

const getSpecificLog = (code, cb) => {
    console.log('getSpecificLog called!');
    let url = `https://classic.warcraftlogs.com/v1/report/fights/${stripCode(code)}?api_key=${config.warcraft_logs.apikey}`;
    doRequest(url, (data, url) => {
        cb(data, url);
    });
}

const getLatestLog = (guildId, cb) => {
    console.log('getLatestLog called!');
    let zone = guildId.split('|')[1];
    let guild = guildId.split('|')[0];
    let url = `https://classic.warcraftlogs.com/v1/reports/guild/${guild}?api_key=${config.warcraft_logs.apikey}`
    doRequest(url, (data, url) => {
        getSpecificLog(data[0].id, cb);
    });
}

const drawOutput = (data, code, cb) => {
    console.log('drawOutput called!');
    let link = `https://classic.warcraftlogs.com/reports/${code}`;
    let embed = {
        title: data.title,
        url: link,
        thumbnail:{ url: 'https://dmszsuqyoe6y6.cloudfront.net/img/warcraft/zones/zone-'+data.zone+'-small.jpg' },
    }
    embed.fields = [{
        name: '\u200b',
        value:  (data.title != raids[data.zone] ? 'Zone: **' + raids[data.zone] + "**\n" : '') +
                'Date: **' + new Date(data.start).toDateString() + "**\n" +
                'Duration: **' + convertMilliseconds(data.end - data.start) + "**\n"
    },{name:'\u200b',value:'Bosses:'}];
    var bosses = [];
    var timer = 0;
    for (var i = 0; i<data.fights.length; i++)
    {
        var fight = data.fights[i];
        if (fight.end_time < timer) break;
        if (fight.boss)
        {
            if (bosses.indexOf(fight.boss)>-1) continue;
            bosses.push(fight.boss);
            var enemy;
            for (var j = 0; j<data.enemies.length; j++)
            {
                enemy = data.enemies[j];
                if (fight.name == enemy.name) break;
                if (data.enemies[j].type != "Boss") enemy = undefined;
            }
            if (!enemy) continue;
            var name = (enemy.name.length > 21 ? enemy.name.split(' ')[0] : enemy.name);
            field = {name: name, value: '', inline: true}
            for (var j = 0; j<enemy.fights.length; j++)
            {
                var spec_fight = data.fights[enemy.fights[j].id-1];
                if (!spec_fight.boss) continue;
                if (field.value.length > 666)
                {
                    fields.push(field);
                    field = {name: name, value: '', inline: true}
                }
                var timeStart = convertMilliseconds(spec_fight.start_time),
                    timeEnd = convertMilliseconds(spec_fight.end_time);
                field.value += (spec_fight.kill ? "✅" : "❌") + " - ";
                field.value += "["+timeEnd;
                field.value += "]("+link+"#fight="+spec_fight.id+" '";
                field.value += enemy.name+" "+(spec_fight.kill?'kill':'wipe')+" from "+timeStart+" to "+timeEnd+" | Length: "+convertMilliseconds(spec_fight.end_time-spec_fight.start_time)+"') "+"\n";
                if (spec_fight.kill) break;
            }

            embed.fields.push(field);
        }
        if (fight.end_time > timer) timer = fight.end_time;
    }
    if (bosses.length % 3 != 0) embed.fields.push({name:"\u200b ",value:"\u200b ",inline:true});
    embed.fields.push({name:'See the full log:',value:link});
    cb.edit({embed: embed});
}

const getWorldbuffs = (code, start, cb) => {
    console.log('getWorldbuffs called!');
    let url = `https://classic.warcraftlogs.com/v1/report/events/${code}?type=combatantinfo&start=${start}&end=${start}&api_key=${config.warcraft_logs.apikey}`;
    doRequest(url, (data, url) => {
        cb(data.events, url);
    });
}

const drawWorldbuffs = (events, log, url, cb) => {
    console.log('drawWorldbuffs called!');
    console.log(`Checking ${log.friendlies.length} friendlies and ${events.length} events`);
    let code = stripCode(url);
    let link = `https://classic.warcraftlogs.com/reports/${code}`;
    let header = {
        title: `Worldbuffs for ${log.title} ${new Date(log.start).toDateString()}`,
        url: link,
        fields: []
    }

    let friendlies = [];

    for (var i = log.friendlies.length - 1; i >= 0; i--) {
        let player = log.friendlies[i];
        if (!player.hasOwnProperty('server')) continue;
        friendlies[player.id] = {name: player.name, type: player.type, icon: player.icon, buffs: []};
    }

    for (var i = events.length - 1; i >= 0; i--) {
        var event = events[i];
        if (event.type !== 'combatantinfo') continue;
        if (event.hasOwnProperty('auras')) {
            for (var j = event.auras.length - 1; j >= 0; j--) {
                var aura = event.auras[j];
                friendlies[event.sourceID].buffs.push({name:aura.name,id:aura.ability,icon:aura.icon});
            }
        }
    }

    const emojis = cb.client.emojis;

    var l = 0;

    var fields = [];

    for (var i = friendlies.length - 1; i >= 0; i--) {
        let player = friendlies[i];
        if(!player) continue;
        const icon = emojis.find(emoji => emoji.name === player.type.split('-')[0]);
        let field = {
            name: `${(icon?icon+' ':'')}${player.name}`,
            value: [],
            inline: true
        };
        for (var j = 0; j < player.buffs.length; j++) {
            let buff = player.buffs[j];
            if (!buff) continue;
            let buffName = buff.name.toLowerCase().replace(/\s/g, 'O').replace(/\W/g, '').replace(/O/g,'_');
            if (buff.icon.split('.')[0] == 'inv_misc_orb_02') buffName = 'sayges_dark_fortune';
            let emoji = emojis.find(emoji => emoji.name === buffName);
            if(!emoji) {
                console.log(`Missing emoji for ${buffName}`);
                emoji = '❔';
            }
            field.value.push(emoji);
            if (j % 4 == 0 && j > 0) {
                field.value.push("\n");
            }
        }
        if(field.name && field.value.length){
            fields.push(field);
        }
    }

    fields.sort((a,b) => {
        return b.value.length - a.value.length;
    })

    let page = 1;
    let slice = 0;

    for (var i = 0; i < fields.length; i++) {
        fields[i].value = fields[i].value.join('').trim()+'\u200b';
        l+=fields[i].value.length+fields[i].name.length;
        if ((l > 5000 && i % 3 == 0) || i == fields.length - 1) {
            embed = JSON.parse(JSON.stringify(header));
            embed.title = `${embed.title} #${page}:`;
            embed.fields = fields.slice(0 + slice, i);
            slice = i;
            l = 0;
            if (page == 1) {
                cb.edit({embed:embed});
            } else {
                cb.channel.send({embed:embed})
            }
            page++;
        }
    }
}

WarcraftLogs.prototype.Message = function(message)
{
    switch (message.channel.type) {
        case 'text':
            var id = message.guild.id;
            break;
        case 'dm':
        case 'group':
            var id = message.channel.id;
            break;
    }

    if (!id) return false;
    var guildId = db.getData('/warcraftlogs/guilds/' + id);
    var zone = guildId.split('|')[1];
    var guild = guildId.split('|')[0];

    var msgArr = message.content.split(" ");

    var action = msgArr[1];
    var aIndex = message.content.indexOf(action);

    switch (action) {
        case 'set':
            if (msgArr.length < 5) {
                message.reply(`You sent too few parameters. Expected syntax: logs [set] [classic|retail region realm Guild Name]`)
                return false;
            }
            var zone = msgArr[4] + '/' + msgArr[3] + "|" + msgArr[2];
            var guildName = msgArr[5];
            for (var i = 6; i <= msgArr.length -1; i++) {
                guildName += " " + msgArr[i];
            }
            guildId = guildName + '/' + zone;
            try {
                getLatestLog(guildId);
                db.push('/warcraftlogs/guilds/' + id, guildId);
            } catch (error) {
                message.reply(`Something went wrong. Expected syntax: logs [set] [classic|retail region realm Guild Name]`)
            }
            break;
        case 'latest':
        case undefined:
            message.channel.send('Getting latest log...')
            .then((sent) => {
                getLatestLog(guildId, (data, url) => {
                    let code = stripCode(url);
                    drawOutput(data, code, sent);
                });
            });
            message.delete();
            break;
        case 'worldbuffs':
            let code = stripCode(msgArr[2]);
            message.channel.send(`Getting worldbuffs for ${code}...`)
            .then((sent) => {
                getSpecificLog(code, (log, url) => {
                    let start = 0;
                    for (var i = log.fights.length - 1; i >= 0; i--) {
                        if (log.fights[i].boss > 0 || log.fights[i].hasOwnProperty('originalBoss')) {
                            start = log.fights[i].start_time;
                        }
                    }
                    getWorldbuffs(code, Math.max(start,0), (data, url) => {
                        drawWorldbuffs(data, log, url, sent);
                    });
                });
            });
            break;
        case 'fix':
            if (msgArr.length < 3) {
                message.reply(`You sent too few parameters. Expected syntax: logs fix msgID url`)
                return false;
            }
            var msgID = msgArr[2];
            var link = msgArr[3];
            message.channel.fetchMessage(msgID)
            .then((sent)=>{
                getSpecificLog(link, (data, url) => {
                    let code = stripCode(url);
                    drawOutput(data, code, sent);
                })
            })
            message.delete();
            break;
        default:
            message.channel.send('Getting specified log...')
            .then((sent) => {
                getSpecificLog(msgArr[1], (data, url) => {
                    let code = stripCode(url);
                    drawOutput(data, code, sent);
                });
            });
            message.delete();
            break;

    }
}

module.exports = WarcraftLogs;
<|endoftext|>


<commit_before>import type { ASTNode } from './ast';

import { visit } from './visitor';
import { printBlockString } from './blockString';

/**
 * Converts an AST into a string, using one set of reasonable
 * formatting rules.
 */
export function print(ast: ASTNode): string {
  return visit(ast, { leave: printDocASTReducer });
}

// TODO: provide better type coverage in future
const printDocASTReducer: any = {
  Name: (node) => node.value,
  Variable: (node) => '$' + node.name,

  // Document

  Document: (node) => join(node.definitions, '\n\n') + '\n',

  OperationDefinition(node) {
    const op = node.operation;
    const name = node.name;
    const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')');
    const directives = join(node.directives, ' ');
    const selectionSet = node.selectionSet;
    // Anonymous queries with no directives or variable definitions can use
    // the query short form.
    return !name && !directives && !varDefs && op === 'query'
      ? selectionSet
      : join([op, join([name, varDefs]), directives, selectionSet], ' ');
  },

  VariableDefinition: ({ variable, type, defaultValue, directives }) =>
    variable +
    ': ' +
    type +
    wrap(' = ', defaultValue) +
    wrap(' ', join(directives, ' ')),
  SelectionSet: ({ selections }) => block(selections),

  Field: ({ alias, name, arguments: args, directives, selectionSet }) =>
    join(
      [
        wrap('', alias, ': ') + name + wrap('(', join(args, ', '), ')'),
        join(directives, ' '),
        selectionSet,
      ],
      ' ',
    ),

  Argument: ({ name, value }) => name + ': ' + value,

  // Fragments

  FragmentSpread: ({ name, directives }) =>
    '...' + name + wrap(' ', join(directives, ' ')),

  InlineFragment: ({ typeCondition, directives, selectionSet }) =>
    join(
      ['...', wrap('on ', typeCondition), join(directives, ' '), selectionSet],
      ' ',
    ),

  FragmentDefinition: ({
    name,
    typeCondition,
    variableDefinitions,
    directives,
    selectionSet,
  }) =>
    // Note: fragment variable definitions are experimental and may be changed
    // or removed in the future.
    `fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
    `on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
    selectionSet,

  // Value

  IntValue: ({ value }) => value,
  FloatValue: ({ value }) => value,
  StringValue: ({ value, block: isBlockString }, key) =>
    isBlockString
      ? printBlockString(value, key === 'description' ? '' : '  ')
      : JSON.stringify(value),
  BooleanValue: ({ value }) => (value ? 'true' : 'false'),
  NullValue: () => 'null',
  EnumValue: ({ value }) => value,
  ListValue: ({ values }) => '[' + join(values, ', ') + ']',
  ObjectValue: ({ fields }) => '{' + join(fields, ', ') + '}',
  ObjectField: ({ name, value }) => name + ': ' + value,

  // Directive

  Directive: ({ name, arguments: args }) =>
    '@' + name + wrap('(', join(args, ', '), ')'),

  // Type

  NamedType: ({ name }) => name,
  ListType: ({ type }) => '[' + type + ']',
  NonNullType: ({ type }) => type + '!',

  // Type System Definitions

  SchemaDefinition: addDescription(({ directives, operationTypes }) =>
    join(['schema', join(directives, ' '), block(operationTypes)], ' '),
  ),

  OperationTypeDefinition: ({ operation, type }) => operation + ': ' + type,

  ScalarTypeDefinition: addDescription(({ name, directives }) =>
    join(['scalar', name, join(directives, ' ')], ' '),
  ),

  ObjectTypeDefinition: addDescription(
    ({ name, interfaces, directives, fields }) =>
      join(
        [
          'type',
          name,
          wrap('implements ', join(interfaces, ' & ')),
          join(directives, ' '),
          block(fields),
        ],
        ' ',
      ),
  ),

  FieldDefinition: addDescription(
    ({ name, arguments: args, type, directives }) =>
      name +
      (hasMultilineItems(args)
        ? wrap('(\n', indent(join(args, '\n')), '\n)')
        : wrap('(', join(args, ', '), ')')) +
      ': ' +
      type +
      wrap(' ', join(directives, ' ')),
  ),

  InputValueDefinition: addDescription(
    ({ name, type, defaultValue, directives }) =>
      join(
        [name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')],
        ' ',
      ),
  ),

  InterfaceTypeDefinition: addDescription(
    ({ name, interfaces, directives, fields }) =>
      join(
        [
          'interface',
          name,
          wrap('implements ', join(interfaces, ' & ')),
          join(directives, ' '),
          block(fields),
        ],
        ' ',
      ),
  ),

  UnionTypeDefinition: addDescription(({ name, directives, types }) =>
    join(
      [
        'union',
        name,
        join(directives, ' '),
        types && types.length !== 0 ? '= ' + join(types, ' | ') : '',
      ],
      ' ',
    ),
  ),

  EnumTypeDefinition: addDescription(({ name, directives, values }) =>
    join(['enum', name, join(directives, ' '), block(values)], ' '),
  ),

  EnumValueDefinition: addDescription(({ name, directives }) =>
    join([name, join(directives, ' ')], ' '),
  ),

  InputObjectTypeDefinition: addDescription(({ name, directives, fields }) =>
    join(['input', name, join(directives, ' '), block(fields)], ' '),
  ),

  DirectiveDefinition: addDescription(
    ({ name, arguments: args, repeatable, locations }) =>
      'directive @' +
      name +
      (hasMultilineItems(args)
        ? wrap('(\n', indent(join(args, '\n')), '\n)')
        : wrap('(', join(args, ', '), ')')) +
      (repeatable ? ' repeatable' : '') +
      ' on ' +
      join(locations, ' | '),
  ),

  SchemaExtension: ({ directives, operationTypes }) =>
    join(['extend schema', join(directives, ' '), block(operationTypes)], ' '),

  ScalarTypeExtension: ({ name, directives }) =>
    join(['extend scalar', name, join(directives, ' ')], ' '),

  ObjectTypeExtension: ({ name, interfaces, directives, fields }) =>
    join(
      [
        'extend type',
        name,
        wrap('implements ', join(interfaces, ' & ')),
        join(directives, ' '),
        block(fields),
      ],
      ' ',
    ),

  InterfaceTypeExtension: ({ name, interfaces, directives, fields }) =>
    join(
      [
        'extend interface',
        name,
        wrap('implements ', join(interfaces, ' & ')),
        join(directives, ' '),
        block(fields),
      ],
      ' ',
    ),

  UnionTypeExtension: ({ name, directives, types }) =>
    join(
      [
        'extend union',
        name,
        join(directives, ' '),
        types && types.length !== 0 ? '= ' + join(types, ' | ') : '',
      ],
      ' ',
    ),

  EnumTypeExtension: ({ name, directives, values }) =>
    join(['extend enum', name, join(directives, ' '), block(values)], ' '),

  InputObjectTypeExtension: ({ name, directives, fields }) =>
    join(['extend input', name, join(directives, ' '), block(fields)], ' '),
};

function addDescription(cb) {
  return (node) => join([node.description, cb(node)], '\n');
}

/**
 * Given maybeArray, print an empty string if it is null or empty, otherwise
 * print all items together separated by separator if provided
 */
function join(maybeArray: ?Array<string>, separator = ''): string {
  return maybeArray?.filter((x) => x).join(separator) ?? '';
}

/**
 * Given array, print each item on its own line, wrapped in an
 * indented "{ }" block.
 */
function block(array: ?Array<string>): string {
  return array && array.length !== 0
    ? '{\n' + indent(join(array, '\n')) + '\n}'
    : '';
}

/**
 * If maybeString is not null or empty, then wrap with start and end, otherwise print an empty string.
 */
function wrap(start: string, maybeString: ?string, end: string = ''): string {
  return maybeString != null && maybeString !== ''
    ? start + maybeString + end
    : '';
}

function indent(str: string): string {
  return str !== '' ? '  ' + str.replace(/\n/g, '\n  ') : '';
}

function isMultiline(str: string): boolean {
  return str.indexOf('\n') !== -1;
}

function hasMultilineItems(maybeArray: ?Array<string>): boolean {
  return maybeArray != null && maybeArray.some(isMultiline);
}
<commit_msg>parser: simplify a few utility functions (#2766)<commit_after>import type { ASTNode } from './ast';

import { visit } from './visitor';
import { printBlockString } from './blockString';

/**
 * Converts an AST into a string, using one set of reasonable
 * formatting rules.
 */
export function print(ast: ASTNode): string {
  return visit(ast, { leave: printDocASTReducer });
}

// TODO: provide better type coverage in future
const printDocASTReducer: any = {
  Name: (node) => node.value,
  Variable: (node) => '$' + node.name,

  // Document

  Document: (node) => join(node.definitions, '\n\n') + '\n',

  OperationDefinition(node) {
    const op = node.operation;
    const name = node.name;
    const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')');
    const directives = join(node.directives, ' ');
    const selectionSet = node.selectionSet;
    // Anonymous queries with no directives or variable definitions can use
    // the query short form.
    return !name && !directives && !varDefs && op === 'query'
      ? selectionSet
      : join([op, join([name, varDefs]), directives, selectionSet], ' ');
  },

  VariableDefinition: ({ variable, type, defaultValue, directives }) =>
    variable +
    ': ' +
    type +
    wrap(' = ', defaultValue) +
    wrap(' ', join(directives, ' ')),
  SelectionSet: ({ selections }) => block(selections),

  Field: ({ alias, name, arguments: args, directives, selectionSet }) =>
    join(
      [
        wrap('', alias, ': ') + name + wrap('(', join(args, ', '), ')'),
        join(directives, ' '),
        selectionSet,
      ],
      ' ',
    ),

  Argument: ({ name, value }) => name + ': ' + value,

  // Fragments

  FragmentSpread: ({ name, directives }) =>
    '...' + name + wrap(' ', join(directives, ' ')),

  InlineFragment: ({ typeCondition, directives, selectionSet }) =>
    join(
      ['...', wrap('on ', typeCondition), join(directives, ' '), selectionSet],
      ' ',
    ),

  FragmentDefinition: ({
    name,
    typeCondition,
    variableDefinitions,
    directives,
    selectionSet,
  }) =>
    // Note: fragment variable definitions are experimental and may be changed
    // or removed in the future.
    `fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
    `on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
    selectionSet,

  // Value

  IntValue: ({ value }) => value,
  FloatValue: ({ value }) => value,
  StringValue: ({ value, block: isBlockString }, key) =>
    isBlockString
      ? printBlockString(value, key === 'description' ? '' : '  ')
      : JSON.stringify(value),
  BooleanValue: ({ value }) => (value ? 'true' : 'false'),
  NullValue: () => 'null',
  EnumValue: ({ value }) => value,
  ListValue: ({ values }) => '[' + join(values, ', ') + ']',
  ObjectValue: ({ fields }) => '{' + join(fields, ', ') + '}',
  ObjectField: ({ name, value }) => name + ': ' + value,

  // Directive

  Directive: ({ name, arguments: args }) =>
    '@' + name + wrap('(', join(args, ', '), ')'),

  // Type

  NamedType: ({ name }) => name,
  ListType: ({ type }) => '[' + type + ']',
  NonNullType: ({ type }) => type + '!',

  // Type System Definitions

  SchemaDefinition: addDescription(({ directives, operationTypes }) =>
    join(['schema', join(directives, ' '), block(operationTypes)], ' '),
  ),

  OperationTypeDefinition: ({ operation, type }) => operation + ': ' + type,

  ScalarTypeDefinition: addDescription(({ name, directives }) =>
    join(['scalar', name, join(directives, ' ')], ' '),
  ),

  ObjectTypeDefinition: addDescription(
    ({ name, interfaces, directives, fields }) =>
      join(
        [
          'type',
          name,
          wrap('implements ', join(interfaces, ' & ')),
          join(directives, ' '),
          block(fields),
        ],
        ' ',
      ),
  ),

  FieldDefinition: addDescription(
    ({ name, arguments: args, type, directives }) =>
      name +
      (hasMultilineItems(args)
        ? wrap('(\n', indent(join(args, '\n')), '\n)')
        : wrap('(', join(args, ', '), ')')) +
      ': ' +
      type +
      wrap(' ', join(directives, ' ')),
  ),

  InputValueDefinition: addDescription(
    ({ name, type, defaultValue, directives }) =>
      join(
        [name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')],
        ' ',
      ),
  ),

  InterfaceTypeDefinition: addDescription(
    ({ name, interfaces, directives, fields }) =>
      join(
        [
          'interface',
          name,
          wrap('implements ', join(interfaces, ' & ')),
          join(directives, ' '),
          block(fields),
        ],
        ' ',
      ),
  ),

  UnionTypeDefinition: addDescription(({ name, directives, types }) =>
    join(
      [
        'union',
        name,
        join(directives, ' '),
        types && types.length !== 0 ? '= ' + join(types, ' | ') : '',
      ],
      ' ',
    ),
  ),

  EnumTypeDefinition: addDescription(({ name, directives, values }) =>
    join(['enum', name, join(directives, ' '), block(values)], ' '),
  ),

  EnumValueDefinition: addDescription(({ name, directives }) =>
    join([name, join(directives, ' ')], ' '),
  ),

  InputObjectTypeDefinition: addDescription(({ name, directives, fields }) =>
    join(['input', name, join(directives, ' '), block(fields)], ' '),
  ),

  DirectiveDefinition: addDescription(
    ({ name, arguments: args, repeatable, locations }) =>
      'directive @' +
      name +
      (hasMultilineItems(args)
        ? wrap('(\n', indent(join(args, '\n')), '\n)')
        : wrap('(', join(args, ', '), ')')) +
      (repeatable ? ' repeatable' : '') +
      ' on ' +
      join(locations, ' | '),
  ),

  SchemaExtension: ({ directives, operationTypes }) =>
    join(['extend schema', join(directives, ' '), block(operationTypes)], ' '),

  ScalarTypeExtension: ({ name, directives }) =>
    join(['extend scalar', name, join(directives, ' ')], ' '),

  ObjectTypeExtension: ({ name, interfaces, directives, fields }) =>
    join(
      [
        'extend type',
        name,
        wrap('implements ', join(interfaces, ' & ')),
        join(directives, ' '),
        block(fields),
      ],
      ' ',
    ),

  InterfaceTypeExtension: ({ name, interfaces, directives, fields }) =>
    join(
      [
        'extend interface',
        name,
        wrap('implements ', join(interfaces, ' & ')),
        join(directives, ' '),
        block(fields),
      ],
      ' ',
    ),

  UnionTypeExtension: ({ name, directives, types }) =>
    join(
      [
        'extend union',
        name,
        join(directives, ' '),
        types && types.length !== 0 ? '= ' + join(types, ' | ') : '',
      ],
      ' ',
    ),

  EnumTypeExtension: ({ name, directives, values }) =>
    join(['extend enum', name, join(directives, ' '), block(values)], ' '),

  InputObjectTypeExtension: ({ name, directives, fields }) =>
    join(['extend input', name, join(directives, ' '), block(fields)], ' '),
};

function addDescription(cb) {
  return (node) => join([node.description, cb(node)], '\n');
}

/**
 * Given maybeArray, print an empty string if it is null or empty, otherwise
 * print all items together separated by separator if provided
 */
function join(maybeArray: ?Array<string>, separator = ''): string {
  return maybeArray?.filter((x) => x).join(separator) ?? '';
}

/**
 * Given array, print each item on its own line, wrapped in an
 * indented "{ }" block.
 */
function block(array: ?Array<string>): string {
  return wrap('{\n', indent(join(array, '\n')), '\n}');
}

/**
 * If maybeString is not null or empty, then wrap with start and end, otherwise print an empty string.
 */
function wrap(start: string, maybeString: ?string, end: string = ''): string {
  return maybeString != null && maybeString !== ''
    ? start + maybeString + end
    : '';
}

function indent(str: string): string {
  return wrap('  ', str.replace(/\n/g, '\n  '));
}

function isMultiline(str: string): boolean {
  return str.indexOf('\n') !== -1;
}

function hasMultilineItems(maybeArray: ?Array<string>): boolean {
  return maybeArray != null && maybeArray.some(isMultiline);
}
<|endoftext|>


<commit_before>import React, { useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import * as FireworksCanvas from 'fireworks-canvas'
import { withStyles } from '@material-ui/core/styles'
import IconButton from '@material-ui/core/IconButton'
import CloseIcon from '@material-ui/icons/Close'
import Typography from '@material-ui/core/Typography'
import SocialShare from 'js/components/General/SocialShareComponent'
import { millionRaisedURL } from 'js/navigation/navigation'

const styles = {
  container: {
    position: 'fixed',
    zIndex: 5000,
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    background: 'rgba(0, 0, 0, 0.85)',
  },
  closeIconButton: {
    position: 'absolute',
    zIndex: 6000,
    top: 10,
    right: 10,
  },
  closeIcon: {
    color: 'rgba(255, 255, 255, 0.8)',
    width: 36,
    height: 36,
  },
  messageContainer: {
    position: 'absolute',
    top: 20,
    left: 20,
    right: 20,
    bottom: 20,
    maxWidth: 800,
  },
  messageText: {
    color: 'white',
  },
  shareContainer: {
    display: 'flex',
    justifyContent: 'flex-start',
    alignItems: 'center',
  },
  shareMessageText: {
    color: 'white',
    marginRight: 12,
  },
}

const FireworksMessage1MComponent = ({ classes }) => {
  return (

      <Typography variant="h2" className={classes.messageText} gutterBottom>
        Congratulations!
      </Typography>
      <Typography variant="h5" className={classes.messageText} gutterBottom>
        $1M raised is incredible milestone, and we couldn't be more proud of the
        Tabbing community. From the bottoms of our hearts, thank you, and
        congrats!
      </Typography>

        <Typography
          variant="h5"
          className={classes.shareMessageText}
          gutterBottom
        >
          Share this achievement:
        </Typography>
        <SocialShare
          url={millionRaisedURL}
          FacebookShareButtonProps={
            {
              // Disabling the quote so Facebook shares the large version
              // of the image, but leaving this prop so that SocialShare
              // will still show the Facebook button.
              // quote:
              //   'Join me in turning your internet browsing into a force for good with @TabForACause',
            }
          }
          RedditShareButtonProps={{
            title:
              'Turn your internet browsing into a force for good with Tab for a Cause',
          }}
          TumblrShareButtonProps={{
            title: 'A simple and free way to make the world a better place',
            caption:
              'Turn your internet browsing into a force for good with Tab for a Cause',
          }}
          TwitterShareButtonProps={{
            title:
              'Turn your internet browsing into a force for good with @TabForACause. Join me! #TabForAMillion',
            related: ['@TabForACause'],
          }}
        />

  )
}
FireworksMessage1MComponent.propTypes = {
  classes: PropTypes.object.isRequired,
}
FireworksMessage1MComponent.defaultProps = {
  classes: {},
}
export const FireworksMessage1M = withStyles(styles)(
  FireworksMessage1MComponent
)

const Fireworks = ({ classes, children, onClose, options }) => {
  const containerEl = useRef(null)
  useEffect(() => {
    const fireworks = new FireworksCanvas(containerEl.current, options)
    const stopFireworks = fireworks.start()
    return stopFireworks
  }, [containerEl, options])
  return (

      <IconButton
        className={classes.closeIconButton}
        onClick={() => {
          onClose()
        }}
      >
        <CloseIcon className={classes.closeIcon} />
      </IconButton>
      {children}

  )
}

Fireworks.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]),
  onClose: PropTypes.func.isRequired,
  options: PropTypes.object,
}

Fireworks.defaultProps = {
  onClose: () => {},
}

export default withStyles(styles)(Fireworks)
<commit_msg>Center the fireworks message<commit_after>import React, { useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import * as FireworksCanvas from 'fireworks-canvas'
import { withStyles } from '@material-ui/core/styles'
import IconButton from '@material-ui/core/IconButton'
import CloseIcon from '@material-ui/icons/Close'
import Typography from '@material-ui/core/Typography'
import SocialShare from 'js/components/General/SocialShareComponent'
import { millionRaisedURL } from 'js/navigation/navigation'

const styles = {
  container: {
    position: 'fixed',
    zIndex: 5000,
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    background: 'rgba(0, 0, 0, 0.85)',
  },
  closeIconButton: {
    position: 'absolute',
    zIndex: 6000,
    top: 10,
    right: 10,
  },
  closeIcon: {
    color: 'rgba(255, 255, 255, 0.8)',
    width: 36,
    height: 36,
  },
  messageContainer: {
    position: 'absolute',
    top: '15%',
    left: 20,
    right: 20,
    bottom: 20,
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
  },
  messageText: {
    color: 'white',
    maxWidth: 840,
  },
  shareContainer: {
    display: 'flex',
    justifyContent: 'flex-start',
    alignItems: 'center',
  },
  shareMessageText: {
    color: 'white',
    marginRight: 12,
  },
}

const FireworksMessage1MComponent = ({ classes }) => {
  return (

      <Typography variant="h2" className={classes.messageText} gutterBottom>
        Congratulations!
      </Typography>
      <Typography
        variant="h5"
        className={classes.messageText}
        align="center"
        gutterBottom
      >
        $1M raised is incredible milestone, and we couldn't be more proud of the
        Tabbing community. From the bottoms of our hearts, thank you, and
        congrats!
      </Typography>

        <Typography
          variant="h5"
          className={classes.shareMessageText}
          align="center"
          gutterBottom
        >
          Share this achievement:
        </Typography>
        <SocialShare
          url={millionRaisedURL}
          FacebookShareButtonProps={
            {
              // Disabling the quote so Facebook shares the large version
              // of the image, but leaving this prop so that SocialShare
              // will still show the Facebook button.
              // quote:
              //   'Join me in turning your internet browsing into a force for good with @TabForACause',
            }
          }
          RedditShareButtonProps={{
            title:
              'Turn your internet browsing into a force for good with Tab for a Cause',
          }}
          TumblrShareButtonProps={{
            title: 'A simple and free way to make the world a better place',
            caption:
              'Turn your internet browsing into a force for good with Tab for a Cause',
          }}
          TwitterShareButtonProps={{
            title:
              'Turn your internet browsing into a force for good with @TabForACause. Join me! #TabForAMillion',
            related: ['@TabForACause'],
          }}
        />

  )
}
FireworksMessage1MComponent.propTypes = {
  classes: PropTypes.object.isRequired,
}
FireworksMessage1MComponent.defaultProps = {
  classes: {},
}
export const FireworksMessage1M = withStyles(styles)(
  FireworksMessage1MComponent
)

const Fireworks = ({ classes, children, onClose, options }) => {
  const containerEl = useRef(null)
  useEffect(() => {
    const fireworks = new FireworksCanvas(containerEl.current, options)
    const stopFireworks = fireworks.start()
    return stopFireworks
  }, [containerEl, options])
  return (

      <IconButton
        className={classes.closeIconButton}
        onClick={() => {
          onClose()
        }}
      >
        <CloseIcon className={classes.closeIcon} />
      </IconButton>
      {children}

  )
}

Fireworks.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]),
  onClose: PropTypes.func.isRequired,
  options: PropTypes.object,
}

Fireworks.defaultProps = {
  onClose: () => {},
}

export default withStyles(styles)(Fireworks)
<|endoftext|>


<commit_before>import { createElement as h, Fragment, useEffect } from 'react'

import {
	OS_SORTING_TOP,
	OS_SORTING_RECENT,
	OS_NO_VERSION,
	OS_WITH_VERSION
} from '../../../../constants/os'

import {
	ALL_TIME,
	LAST_7_DAYS,
	LAST_30_DAYS
} from '../../../../constants/dateRange'

import enhanceOs from '../../enhancers/enhanceOs'
import useDidMountEffect from '../../utils/useDidMountEffect'

import CardOs from '../cards/CardOs'
import Select from '../Select'

const RouteOs = (props) => {

	useEffect(() => {

		props.fetchDomains(props)

	}, [])

	useDidMountEffect(() => {

		props.domains.value.map((domain) => {
			props.fetchOs(props, domain.data.id)
		})

	}, [ props.domains.value, props.os.sorting, props.os.type, props.os.dateRange ])

	return (
		h(Fragment, {},

			h('div', { className: 'subHeader' },
				h(Select, {
					value: props.os.sorting,
					onChange: (e) => props.setOsSorting(e.target.value),
					items: [
						{ value: OS_SORTING_TOP, label: 'Top OS' },
						{ value: OS_SORTING_RECENT, label: 'Recent OS' }
					]
				}),
				h(Select, {
					value: props.os.type,
					onChange: (e) => props.setOsType(e.target.value),
					items: [
						{ value: OS_NO_VERSION, label: 'No version' },
						{ value: OS_WITH_VERSION, label: 'With version' }
					]
				}),
				h(Select, {
					disabled: props.os.sorting !== OS_SORTING_TOP,
					value: props.os.dateRange,
					onChange: (e) => props.setOsTopDateRange(e.target.value),
					items: [
						{ value: LAST_7_DAYS.value, label: LAST_7_DAYS.label },
						{ value: LAST_30_DAYS.value, label: LAST_30_DAYS.label },
						{ value: ALL_TIME.value, label: ALL_TIME.label }
					]
				}),
				h(Select, {
					disabled: props.os.sorting !== OS_SORTING_TOP,
					value: props.os.dateRange,
					onChange: (e) => props.setOsTopDateRange(e.target.value),
					items: [
						{ value: LAST_7_DAYS.value, label: LAST_7_DAYS.label },
						{ value: LAST_30_DAYS.value, label: LAST_30_DAYS.label },
						{ value: ALL_TIME.value, label: ALL_TIME.label }
					]
				})
			),

			props.domains.value.map(
				(domain) => (
					h(CardOs, {
						key: domain.data.id,
						headline: domain.data.title,
						dateRange: props.os.dateRange,
						sorting: props.os.sorting,
						loading: props.os.value[domain.data.id] == null ? false : props.os.value[domain.data.id].fetching,
						items: props.os.value[domain.data.id] == null ? [] : enhanceOs(props.os.value[domain.data.id].value)
					})
				)
			)

		)
	)

}

export default RouteOs<commit_msg>Fix duplicated date range selector<commit_after>import { createElement as h, Fragment, useEffect } from 'react'

import {
	OS_SORTING_TOP,
	OS_SORTING_RECENT,
	OS_NO_VERSION,
	OS_WITH_VERSION
} from '../../../../constants/os'

import {
	ALL_TIME,
	LAST_7_DAYS,
	LAST_30_DAYS
} from '../../../../constants/dateRange'

import enhanceOs from '../../enhancers/enhanceOs'
import useDidMountEffect from '../../utils/useDidMountEffect'

import CardOs from '../cards/CardOs'
import Select from '../Select'

const RouteOs = (props) => {

	useEffect(() => {

		props.fetchDomains(props)

	}, [])

	useDidMountEffect(() => {

		props.domains.value.map((domain) => {
			props.fetchOs(props, domain.data.id)
		})

	}, [ props.domains.value, props.os.sorting, props.os.type, props.os.dateRange ])

	return (
		h(Fragment, {},

			h('div', { className: 'subHeader' },
				h(Select, {
					value: props.os.sorting,
					onChange: (e) => props.setOsSorting(e.target.value),
					items: [
						{ value: OS_SORTING_TOP, label: 'Top OS' },
						{ value: OS_SORTING_RECENT, label: 'Recent OS' }
					]
				}),
				h(Select, {
					value: props.os.type,
					onChange: (e) => props.setOsType(e.target.value),
					items: [
						{ value: OS_NO_VERSION, label: 'No version' },
						{ value: OS_WITH_VERSION, label: 'With version' }
					]
				}),
				h(Select, {
					disabled: props.os.sorting !== OS_SORTING_TOP,
					value: props.os.dateRange,
					onChange: (e) => props.setOsTopDateRange(e.target.value),
					items: [
						{ value: LAST_7_DAYS.value, label: LAST_7_DAYS.label },
						{ value: LAST_30_DAYS.value, label: LAST_30_DAYS.label },
						{ value: ALL_TIME.value, label: ALL_TIME.label }
					]
				})
			),

			props.domains.value.map(
				(domain) => (
					h(CardOs, {
						key: domain.data.id,
						headline: domain.data.title,
						dateRange: props.os.dateRange,
						sorting: props.os.sorting,
						loading: props.os.value[domain.data.id] == null ? false : props.os.value[domain.data.id].fetching,
						items: props.os.value[domain.data.id] == null ? [] : enhanceOs(props.os.value[domain.data.id].value)
					})
				)
			)

		)
	)

}

export default RouteOs<|endoftext|>


<commit_before>export class TreeViewPane {
    constructor(viewPaneName, treeApi) {
        this.viewPaneName = viewPaneName;
        this.treeApi = treeApi;
        this.$flatItems = [];

        this.itemHeight = '50px';
        this.itemsPerPage = 25;
    }

    setItemsPerPage(rpp) {
        this.itemsPerPage = rpp;
        return this;
    }

    setItemHeight(height) {
        this.itemHeight = height;
        return this;
    }

    update(notifyTree = true) {
        this.refreshFilter();
        if (this.dirCtrl) {
            this.dirCtrl.refresh();
        }

        if (this.autoHeight) this._setAutoItemsPerPage();
        if (notifyTree) this.treeApi.onViewUpdate(this);
    }

    _setDirectiveController(directiveController) {
        this.dirCtrl = directiveController;
        this.dirCtrl.viewPane = this;

        if (this.itemsPerPage === 'auto') {
            this.autoHeight = true;
            this._setAutoItemsPerPage();
        }

    }

    _setAutoItemsPerPage() {
        this.treeApi.$timeout(() => {
            let containerHeight = $(this.dirCtrl.$element).parent().height();
            this.containerHeight = containerHeight;
            this.setItemsPerPage(Math.floor(containerHeight / parseFloat(this.itemHeight)));
        })
    }

    _getPageData() {
        let prePage = this._getPrePagedData();
/*
        return prePage.slice(this.dirCtrl.virtualScrollIndex,
            this.dirCtrl.virtualScrollIndex + this.itemsPerPage + (this.dirCtrl.virtualScrollIndex + this.itemsPerPage < prePage.length ? 1 : 0));
*/
        return prePage.slice(this.dirCtrl.virtualScrollIndex,
            this.dirCtrl.virtualScrollIndex + this.itemsPerPage + 2);
    }

    _getPrePagedData(ignoreFreeze = false) {
        return this._getSortedData(this._getFilteredData(this._getRawData(ignoreFreeze), ignoreFreeze));
    }

    _getSortedData(sourceData) {
        return sourceData;
    }

    _getFilteredData(sourceData, ignoreCache = false) {
        sourceData = sourceData || this._getRawData(ignoreCache);
        if (this.treeApi.filterCallback && sourceData.length) {
            let filterCache;
            if (!this.filterCache || ignoreCache) {
                filterCache = _.filter(sourceData, item => {
                    let parentIsFilteredOut = false;
                    let curr = item;
                    while (curr.parent) {
                        curr = curr.parent;
                    }
                    if (curr.data !== this.treeApi.GO_UP_NODE && !this.treeApi.filterCallback(curr.data)) {
                        parentIsFilteredOut = true;
                    }

                    return item.data === this.treeApi.GO_UP_NODE || !parentIsFilteredOut && this.treeApi.filterCallback(item.data);
                })
            }
            if (ignoreCache) {
                return filterCache || [];
            }
            else if (filterCache) {
                this.filterCache = filterCache;
                return this.filterCache;
            }
            else {
                return this.filterCache;
            }
        }
        else {
            return sourceData;
        }
    }

    refreshFilter(full = false) {
        if (full) this.$flatItems.forEach(fi => {
            if (fi.data && fi.data.$$$filterResultCache !== undefined) {
                delete fi.data.$$$filterResultCache;
            }
        });
        delete this.filterCache;
    }

    _getRawData(ignoreFreeze = false) {
        return (!ignoreFreeze ? this.$freezedItems : null) || this.$flatItems || [];
    }

    _freeze() {
        if (this.treeApi.$masterFreeze) return;
        this.$freezedItems = [].concat(this.$flatItems);
        this.$freezedOpened = [].concat(this.treeApi.$openedNodes);
        this.treeApi._freezeSelected();
        this.$freezed = true;
    }

    _unFreeze() {
        if (this.treeApi.$masterFreeze) return;

        delete this.$freezedItems;
        delete this.$freezedOpened;
        this.treeApi._unFreezeSelected();
        this.$freezed = false;

        this.refreshFilter();
        this.dirCtrl.syncFakeScroller();
    }

    _addChildren(children, level = 0, parent = null) {
        let parentIndex = this.$flatItems.indexOf(parent);
        let added = [];
        children.forEach((node) => {
            let flatItem = this._createFlatItem(node, level, parent);
            added.push(flatItem);
            if (this.treeApi.isNodeOpen(node, true)) {
                this.treeApi.getChildren(node).then(_children => {
                    if (_children && _children.length) {
                        this._addChildren(_children, level + 1, flatItem);
                    }
                })
            }
        })
        let before = this.$flatItems.slice(0, parentIndex + 1);
        let after = this.$flatItems.slice(parentIndex + 1);
        this.$flatItems = before.concat(added).concat(after);
        this.update();
        return added;
    }

    _removeChildren(parent) {
        this.$flatItems = _.filter(this.$flatItems, flat => {
            let remove = false;
            let _parent = flat.parent;
            while (_parent) {
                if (_parent  === parent) {
                    remove = true;
                    break;
                }
                _parent = _parent.parent;
            }
            return !remove;
        })
        this.update();

    }

    _deleteItem(item) {
        _.remove(this.$flatItems, fi => fi === item);
        this._removeChildren(item);
    }

    _buildFlatItems() {
        this.$flatItems = [];
        let paneRoot = _.filter(this.treeApi.$root, node => {
            return this.treeApi.paneSelector(node) === this.viewPaneName;
        })
        this._addChildren(paneRoot);
    }

    _createFlatItem(node, level = 0, parent = null) {
        let flat = {
            pane: this,
            data: node,
            level,
            parent,
            hasChildren: undefined
        }

        if (this.treeApi.childrenChecker) {
            let check = this.treeApi.childrenChecker(node);
            if (check && check.then) {
                check.then((_check) => {
                    flat.hasChildren = _check;
                })
            }
            else flat.hasChildren = check;
        }
        else {
            this.treeApi.getChildren(node).then(children => {
                flat.hasChildren = !!(children && children.length);
            });
        }

        return flat;
    }

    _recursiveOpenRestore(node) {
        let defer = this.treeApi.$q.defer();

        let id = this.treeApi.uniqueIdGetter(node);
        let opened = _.find(this.treeApi.$openedNodes, n => this.treeApi.uniqueIdGetter(n) === id);
        if (opened) {
            _.remove(this.treeApi.$openedNodes, n => n === opened);
            this.treeApi.openNode(node).then(() => {
                let children = node.$childrenCache;
                if (!children || !children.length) defer.resolve();
                else {
                    let pendingPromises = children.length;
                    children.forEach(child => {
                        this._recursiveOpenRestore(child).then(() => {
                            pendingPromises--;
                            if (pendingPromises === 0) {
                                defer.resolve();
                            }
                        })
                    })
                }
            })
        }
        else {
            defer.resolve();
        }

        return defer.promise;
    }

    refreshNode(node) {
        let defer = this.treeApi.$q.defer();
        let flat = this._flatFromNode(node);
        if (flat) {
            this._freeze();
            this._removeChildren(flat);
            delete flat.data.$childrenCache;
            this.refreshNodeContextMenu(flat.data);
            this._recursiveOpenRestore(flat.data).then(() => {
                this._unFreeze();
                defer.resolve();
            });
        }
        return defer.promise;
    }

    refreshNodeContextMenu(node) {
        delete node.$cachedCMItems;
    }

    refreshView() {
        let mainDefer = this.treeApi.$q.defer();

        this._freeze();
        this.treeApi._getRoot().then(() => {
            if (this.treeApi.uniqueIdGetter) {
                let resolveCount = 0;
                let itemsCount = this.$flatItems.length;
                if (!this.$flatItems.length) mainDefer.resolve();
                this.$flatItems.forEach((fi, ind) => {
                    this._recursiveOpenRestore(fi.data).then(() => {
                        resolveCount++;
                        if (resolveCount === itemsCount) {
                            let selectedId = this.treeApi.$selectedNode ? this.treeApi.uniqueIdGetter(this.treeApi.$selectedNode) : null;
                            let newSelected = selectedId !== null ? _.find(this.$flatItems, fi => fi.data !== this.treeApi.GO_UP_NODE && this.treeApi.uniqueIdGetter(fi.data) === selectedId) : null;
                            if (newSelected) {

                                this.treeApi._setSelected(newSelected);
                                this._unFreeze();
                                mainDefer.resolve();
                            }
                            else if (selectedId) {
                                this.treeApi.nodeByIdGetter(selectedId).then(node => {
                                    this._unFreeze();
                                    this.treeApi.openDeepNode(node).then(() => {
                                        mainDefer.resolve();
                                    })
                                }).catch(() => {
                                    this.selectFirst();
                                    this._unFreeze();
                                    mainDefer.resolve();
                                })
                            }
                            else {
                                this._unFreeze();
                                mainDefer.resolve();
                            }
                        }
                    })
                })
            }
        })

        return mainDefer.promise;
    }

    selectFirst() {
        if (this._getPrePagedData().length) this.treeApi._setSelected(this._getPrePagedData()[0])
    }

    getQuickFindMatches() {
        if (!this.treeApi.quickFindTerm) return [];
        else {
            let matches = _.filter(this.$flatItems, (fi, ind) => {
                let text = this.treeApi.textGetter(fi.data);
                let matchObj = this.treeApi.AdvancedStringMatch.match(text, this.treeApi.quickFindTerm);
                let matched = matchObj ? matchObj.matched : null;
                if (matched) fi.$$index = ind;
                return matched;
            })

            if (this.treeApi.$selectedNode) {
                let selectedIndex = _.findIndex(this.$flatItems, fi => fi.data === this.treeApi.$selectedNode);
                let matchesAfterSelection = _.filter(matches, match => {
                    return match.$$index >= selectedIndex;
                });
                let matchesBeforeSelection = _.difference(matches, matchesAfterSelection);

                matches = matchesAfterSelection.concat(matchesBeforeSelection);
            }

            return matches;
        }
    }

    centerOnNode(node) {
        let flat = this._flatFromNode(node);
        if (flat) this.centerOnItem(flat);
    }

    _flatFromNode(node) {
        let refMatch = _.find(this.$flatItems, flat => flat.data === node);
        if (!refMatch) {
            let nodeId = this.treeApi.uniqueIdGetter(node);
            let idMatch = _.find(this.$flatItems, flat => {
                if (flat.data === this.treeApi.GO_UP_NODE) return false;
                else {
                    let flatId = this.treeApi.uniqueIdGetter(flat.data);
                    return  flatId === nodeId;
                }
            });
            return idMatch;
        }
        return refMatch;
    }

    bringItemToView(item) {
        let prePaged = this._getPrePagedData(true);
        let index = prePaged.indexOf(item);
        let halfPage = Math.floor(this.itemsPerPage / 2);

        if (index - 1 < this.dirCtrl.virtualScrollIndex) {
            this.dirCtrl.virtualScrollIndex = index;
            this.dirCtrl.virtScrollDisplacement = 0;
        }
        else if (index + 1 > this.dirCtrl.virtualScrollIndex + this.itemsPerPage) {
            let fullItems = this.containerHeight ? Math.floor(this.containerHeight/parseFloat(this.itemHeight)) : this.itemsPerPage;
            this.dirCtrl.virtualScrollIndex = index - fullItems >= 0 ? index - fullItems : 0;
            let displace = this.containerHeight ? 1-(this.containerHeight/parseFloat(this.itemHeight) - fullItems) : 1;
            this.dirCtrl.virtScrollDisplacement = displace;
        }
        this.dirCtrl.syncFakeScroller(false);

    }

    centerOnItem(item) {
        let prePaged = this._getPrePagedData(true);
        let index = prePaged.indexOf(item);
        let halfPage = Math.floor(this.itemsPerPage / 2);
        if (prePaged.length <= this.itemsPerPage || index - halfPage < 0) {
            this.dirCtrl.virtualScrollIndex = 0;
        }
        else if (index + (this.itemsPerPage - halfPage) > prePaged.length) {
            this.dirCtrl.virtualScrollIndex = prePaged.length - this.itemsPerPage;
        }
        else {
            this.dirCtrl.virtualScrollIndex = index - halfPage;
        }

        this.dirCtrl.syncFakeScroller(false);
        this.treeApi._setSelected(item);
    }

    focus() {
        if (this.dirCtrl) $(this.dirCtrl.$element).find('.jf-tree').focus();
    }

    findNode(findFunction) {
        let item = _.find(this.$flatItems, fi => {
            return fi.data !== this.treeApi.GO_UP_NODE && findFunction(fi.data);
        })
        if (item) return item.data;
    }

    findNodeByUniqueId(uniqueId) {
        let item = _.find(this.$flatItems, fi => {
            return fi.data !== this.treeApi.GO_UP_NODE && this.treeApi.uniqueIdGetter(fi.data) === uniqueId;
        })
        if (item) return item.data;
    }

    isNodeOpen(node, ignoreFreeze = false) {
        return (!ignoreFreeze && this.$freezedOpened && _.includes(this.$freezedOpened, node)) || (!this.$freezedOpened && _.includes(this.treeApi.$openedNodes, node));
    }

    getNodesCount() {
        return this._getRawData().length;
    }

    getFilteredNodesCount() {
        return _.filter(this._getFilteredData(), fi => fi.data !== this.treeApi.GO_UP_NODE).length;
    }

}<commit_msg>jf-tree: When deleting a node, also remove it from its parent's children cache.<commit_after>export class TreeViewPane {
    constructor(viewPaneName, treeApi) {
        this.viewPaneName = viewPaneName;
        this.treeApi = treeApi;
        this.$flatItems = [];

        this.itemHeight = '50px';
        this.itemsPerPage = 25;
    }

    setItemsPerPage(rpp) {
        this.itemsPerPage = rpp;
        return this;
    }

    setItemHeight(height) {
        this.itemHeight = height;
        return this;
    }

    update(notifyTree = true) {
        this.refreshFilter();
        if (this.dirCtrl) {
            this.dirCtrl.refresh();
        }

        if (this.autoHeight) this._setAutoItemsPerPage();
        if (notifyTree) this.treeApi.onViewUpdate(this);
    }

    _setDirectiveController(directiveController) {
        this.dirCtrl = directiveController;
        this.dirCtrl.viewPane = this;

        if (this.itemsPerPage === 'auto') {
            this.autoHeight = true;
            this._setAutoItemsPerPage();
        }

    }

    _setAutoItemsPerPage() {
        this.treeApi.$timeout(() => {
            let containerHeight = $(this.dirCtrl.$element).parent().height();
            this.containerHeight = containerHeight;
            this.setItemsPerPage(Math.floor(containerHeight / parseFloat(this.itemHeight)));
        })
    }

    _getPageData() {
        let prePage = this._getPrePagedData();
/*
        return prePage.slice(this.dirCtrl.virtualScrollIndex,
            this.dirCtrl.virtualScrollIndex + this.itemsPerPage + (this.dirCtrl.virtualScrollIndex + this.itemsPerPage < prePage.length ? 1 : 0));
*/
        return prePage.slice(this.dirCtrl.virtualScrollIndex,
            this.dirCtrl.virtualScrollIndex + this.itemsPerPage + 2);
    }

    _getPrePagedData(ignoreFreeze = false) {
        return this._getSortedData(this._getFilteredData(this._getRawData(ignoreFreeze), ignoreFreeze));
    }

    _getSortedData(sourceData) {
        return sourceData;
    }

    _getFilteredData(sourceData, ignoreCache = false) {
        sourceData = sourceData || this._getRawData(ignoreCache);
        if (this.treeApi.filterCallback && sourceData.length) {
            let filterCache;
            if (!this.filterCache || ignoreCache) {
                filterCache = _.filter(sourceData, item => {
                    let parentIsFilteredOut = false;
                    let curr = item;
                    while (curr.parent) {
                        curr = curr.parent;
                    }
                    if (curr.data !== this.treeApi.GO_UP_NODE && !this.treeApi.filterCallback(curr.data)) {
                        parentIsFilteredOut = true;
                    }

                    return item.data === this.treeApi.GO_UP_NODE || !parentIsFilteredOut && this.treeApi.filterCallback(item.data);
                })
            }
            if (ignoreCache) {
                return filterCache || [];
            }
            else if (filterCache) {
                this.filterCache = filterCache;
                return this.filterCache;
            }
            else {
                return this.filterCache;
            }
        }
        else {
            return sourceData;
        }
    }

    refreshFilter(full = false) {
        if (full) this.$flatItems.forEach(fi => {
            if (fi.data && fi.data.$$$filterResultCache !== undefined) {
                delete fi.data.$$$filterResultCache;
            }
        });
        delete this.filterCache;
    }

    _getRawData(ignoreFreeze = false) {
        return (!ignoreFreeze ? this.$freezedItems : null) || this.$flatItems || [];
    }

    _freeze() {
        if (this.treeApi.$masterFreeze) return;
        this.$freezedItems = [].concat(this.$flatItems);
        this.$freezedOpened = [].concat(this.treeApi.$openedNodes);
        this.treeApi._freezeSelected();
        this.$freezed = true;
    }

    _unFreeze() {
        if (this.treeApi.$masterFreeze) return;

        delete this.$freezedItems;
        delete this.$freezedOpened;
        this.treeApi._unFreezeSelected();
        this.$freezed = false;

        this.refreshFilter();
        this.dirCtrl.syncFakeScroller();
    }

    _addChildren(children, level = 0, parent = null) {
        let parentIndex = this.$flatItems.indexOf(parent);
        let added = [];
        children.forEach((node) => {
            let flatItem = this._createFlatItem(node, level, parent);
            added.push(flatItem);
            if (this.treeApi.isNodeOpen(node, true)) {
                this.treeApi.getChildren(node).then(_children => {
                    if (_children && _children.length) {
                        this._addChildren(_children, level + 1, flatItem);
                    }
                })
            }
        })
        let before = this.$flatItems.slice(0, parentIndex + 1);
        let after = this.$flatItems.slice(parentIndex + 1);
        this.$flatItems = before.concat(added).concat(after);
        this.update();
        return added;
    }

    _removeChildren(parent) {
        this.$flatItems = _.filter(this.$flatItems, flat => {
            let remove = false;
            let _parent = flat.parent;
            while (_parent) {
                if (_parent  === parent) {
                    remove = true;
                    break;
                }
                _parent = _parent.parent;
            }
            return !remove;
        })
        this.update();

    }

    _deleteItem(item) {
        _.remove(this.$flatItems, fi => fi === item);
        _.remove(item.parent.data.$childrenCache, node => node === item.data);
        this._removeChildren(item);
    }

    _buildFlatItems() {
        this.$flatItems = [];
        let paneRoot = _.filter(this.treeApi.$root, node => {
            return this.treeApi.paneSelector(node) === this.viewPaneName;
        })
        this._addChildren(paneRoot);
    }

    _createFlatItem(node, level = 0, parent = null) {
        let flat = {
            pane: this,
            data: node,
            level,
            parent,
            hasChildren: undefined
        }

        if (this.treeApi.childrenChecker) {
            let check = this.treeApi.childrenChecker(node);
            if (check && check.then) {
                check.then((_check) => {
                    flat.hasChildren = _check;
                })
            }
            else flat.hasChildren = check;
        }
        else {
            this.treeApi.getChildren(node).then(children => {
                flat.hasChildren = !!(children && children.length);
            });
        }

        return flat;
    }

    _recursiveOpenRestore(node) {
        let defer = this.treeApi.$q.defer();

        let id = this.treeApi.uniqueIdGetter(node);
        let opened = _.find(this.treeApi.$openedNodes, n => this.treeApi.uniqueIdGetter(n) === id);
        if (opened) {
            _.remove(this.treeApi.$openedNodes, n => n === opened);
            this.treeApi.openNode(node).then(() => {
                let children = node.$childrenCache;
                if (!children || !children.length) defer.resolve();
                else {
                    let pendingPromises = children.length;
                    children.forEach(child => {
                        this._recursiveOpenRestore(child).then(() => {
                            pendingPromises--;
                            if (pendingPromises === 0) {
                                defer.resolve();
                            }
                        })
                    })
                }
            })
        }
        else {
            defer.resolve();
        }

        return defer.promise;
    }

    refreshNode(node) {
        let defer = this.treeApi.$q.defer();
        let flat = this._flatFromNode(node);
        if (flat) {
            this._freeze();
            this._removeChildren(flat);
            delete flat.data.$childrenCache;
            this.refreshNodeContextMenu(flat.data);
            this._recursiveOpenRestore(flat.data).then(() => {
                this._unFreeze();
                defer.resolve();
            });
        }
        return defer.promise;
    }

    refreshNodeContextMenu(node) {
        delete node.$cachedCMItems;
    }

    refreshView() {
        let mainDefer = this.treeApi.$q.defer();

        this._freeze();
        this.treeApi._getRoot().then(() => {
            if (this.treeApi.uniqueIdGetter) {
                let resolveCount = 0;
                let itemsCount = this.$flatItems.length;
                if (!this.$flatItems.length) mainDefer.resolve();
                this.$flatItems.forEach((fi, ind) => {
                    this._recursiveOpenRestore(fi.data).then(() => {
                        resolveCount++;
                        if (resolveCount === itemsCount) {
                            let selectedId = this.treeApi.$selectedNode ? this.treeApi.uniqueIdGetter(this.treeApi.$selectedNode) : null;
                            let newSelected = selectedId !== null ? _.find(this.$flatItems, fi => fi.data !== this.treeApi.GO_UP_NODE && this.treeApi.uniqueIdGetter(fi.data) === selectedId) : null;
                            if (newSelected) {

                                this.treeApi._setSelected(newSelected);
                                this._unFreeze();
                                mainDefer.resolve();
                            }
                            else if (selectedId) {
                                this.treeApi.nodeByIdGetter(selectedId).then(node => {
                                    this._unFreeze();
                                    this.treeApi.openDeepNode(node).then(() => {
                                        mainDefer.resolve();
                                    })
                                }).catch(() => {
                                    this.selectFirst();
                                    this._unFreeze();
                                    mainDefer.resolve();
                                })
                            }
                            else {
                                this._unFreeze();
                                mainDefer.resolve();
                            }
                        }
                    })
                })
            }
        })

        return mainDefer.promise;
    }

    selectFirst() {
        if (this._getPrePagedData().length) this.treeApi._setSelected(this._getPrePagedData()[0])
    }

    getQuickFindMatches() {
        if (!this.treeApi.quickFindTerm) return [];
        else {
            let matches = _.filter(this.$flatItems, (fi, ind) => {
                let text = this.treeApi.textGetter(fi.data);
                let matchObj = this.treeApi.AdvancedStringMatch.match(text, this.treeApi.quickFindTerm);
                let matched = matchObj ? matchObj.matched : null;
                if (matched) fi.$$index = ind;
                return matched;
            })

            if (this.treeApi.$selectedNode) {
                let selectedIndex = _.findIndex(this.$flatItems, fi => fi.data === this.treeApi.$selectedNode);
                let matchesAfterSelection = _.filter(matches, match => {
                    return match.$$index >= selectedIndex;
                });
                let matchesBeforeSelection = _.difference(matches, matchesAfterSelection);

                matches = matchesAfterSelection.concat(matchesBeforeSelection);
            }

            return matches;
        }
    }

    centerOnNode(node) {
        let flat = this._flatFromNode(node);
        if (flat) this.centerOnItem(flat);
    }

    _flatFromNode(node) {
        let refMatch = _.find(this.$flatItems, flat => flat.data === node);
        if (!refMatch) {
            let nodeId = this.treeApi.uniqueIdGetter(node);
            let idMatch = _.find(this.$flatItems, flat => {
                if (flat.data === this.treeApi.GO_UP_NODE) return false;
                else {
                    let flatId = this.treeApi.uniqueIdGetter(flat.data);
                    return  flatId === nodeId;
                }
            });
            return idMatch;
        }
        return refMatch;
    }

    bringItemToView(item) {
        let prePaged = this._getPrePagedData(true);
        let index = prePaged.indexOf(item);
        let halfPage = Math.floor(this.itemsPerPage / 2);

        if (index - 1 < this.dirCtrl.virtualScrollIndex) {
            this.dirCtrl.virtualScrollIndex = index;
            this.dirCtrl.virtScrollDisplacement = 0;
        }
        else if (index + 1 > this.dirCtrl.virtualScrollIndex + this.itemsPerPage) {
            let fullItems = this.containerHeight ? Math.floor(this.containerHeight/parseFloat(this.itemHeight)) : this.itemsPerPage;
            this.dirCtrl.virtualScrollIndex = index - fullItems >= 0 ? index - fullItems : 0;
            let displace = this.containerHeight ? 1-(this.containerHeight/parseFloat(this.itemHeight) - fullItems) : 1;
            this.dirCtrl.virtScrollDisplacement = displace;
        }
        this.dirCtrl.syncFakeScroller(false);

    }

    centerOnItem(item) {
        let prePaged = this._getPrePagedData(true);
        let index = prePaged.indexOf(item);
        let halfPage = Math.floor(this.itemsPerPage / 2);
        if (prePaged.length <= this.itemsPerPage || index - halfPage < 0) {
            this.dirCtrl.virtualScrollIndex = 0;
        }
        else if (index + (this.itemsPerPage - halfPage) > prePaged.length) {
            this.dirCtrl.virtualScrollIndex = prePaged.length - this.itemsPerPage;
        }
        else {
            this.dirCtrl.virtualScrollIndex = index - halfPage;
        }

        this.dirCtrl.syncFakeScroller(false);
        this.treeApi._setSelected(item);
    }

    focus() {
        if (this.dirCtrl) $(this.dirCtrl.$element).find('.jf-tree').focus();
    }

    findNode(findFunction) {
        let item = _.find(this.$flatItems, fi => {
            return fi.data !== this.treeApi.GO_UP_NODE && findFunction(fi.data);
        })
        if (item) return item.data;
    }

    findNodeByUniqueId(uniqueId) {
        let item = _.find(this.$flatItems, fi => {
            return fi.data !== this.treeApi.GO_UP_NODE && this.treeApi.uniqueIdGetter(fi.data) === uniqueId;
        })
        if (item) return item.data;
    }

    isNodeOpen(node, ignoreFreeze = false) {
        return (!ignoreFreeze && this.$freezedOpened && _.includes(this.$freezedOpened, node)) || (!this.$freezedOpened && _.includes(this.treeApi.$openedNodes, node));
    }

    getNodesCount() {
        return this._getRawData().length;
    }

    getFilteredNodesCount() {
        return _.filter(this._getFilteredData(), fi => fi.data !== this.treeApi.GO_UP_NODE).length;
    }

}<|endoftext|>