<commit_before>import BaseManager from '../common/apiManager.js';
import { mergePost } from '../common/init.js';
import ApiMerger     from '../common/merger.js';

const SockJS = require('sockjs-client');

class ApiManager extends BaseManager
{
    constructor()
    {
        super();
        this.isClient   = true;
        this.formPoster = __FormPosterReqPath;
        this.socketConnected = false;
        this.saltApiForcers  = {};

        if (!this.token)
            this.generateToken();
        else if (this.token.length < 10 || this.token.length > 20)
            this.generateToken();

        this.socketConnect();
        this.deleteOldCache();

        const _this = this;

        Vue.mixin({
            created()
            {
                try
                {
                    if(this.$options.preload && this.api)
                    {
                        const post     = _this.mergePost(this.api_data);
                        const api_salt = _this.getSalt(this.api, post);

                        if(window.API_DATA && window.API_DATA[api_salt])
                        {
                            ApiMerger(this, window.API_DATA[api_salt]);
                        }
                    }

                    if(this.api)
                    {
                        _this.bindVue(this, this.api, this.api_data);
                    }
                }
                catch(e)
                {
                    console.error(e);
                }
            }

        });
    }

    socketConnect()
    {
        if (!this.isClient)
            return null;

        if (this.socket) {
            try {
                console.warn('Closing old socket');
                this.socket.close();
            } catch (e) {
                console.error(e);
            }
        }

        var _this = this;
        const socket = new SockJS(this.base() + 'socketapi?token=' + this.token);
        this.socket = socket
        this.socket.onopen = function () {
            _this.socket = socket
            _this.socketConnected = true;
            _this.emitSocketOpen();
        };
        this.socket.onmessage = function (e) {
            _this.socketConnected = true;
            _this.emitSocketMessage(e);
        };
        this.socket.onclose = function () {
            _this.socketConnected = false;
            _this.emitSocketClose();
        };
    }

    base()
    {
        if (this.base_url)
            return this.base_url;

        var base = location.href;
        var index = base.indexOf('//');

        if (index != -1) {
            index = base.indexOf('/', index + 3);
            if (index != -1) {
                this.base_url = base.substr(0, index + 1);
                return this.base_url;
            }
        }

        return '';
    }

    //---------------------------------------------------------

    getCookie(name) {
        var dc = document.cookie;
        var prefix = name + "=";

        var begin = dc.indexOf("; " + prefix);
        var beginLength = 2 + prefix.length;

        if (begin == -1) {
            begin = dc.indexOf(";" + prefix);
            beginLength--;

            if (begin == -1 && dc.indexOf(prefix) == 0) {
                begin = 0;
                beginLength--;
            } else {
                return '';
            }
        }

        begin += beginLength;
        const end = dc.indexOf(';', begin);

        if (end == -1)
            return dc.substr(begin);

        return unescape(dc.substr(begin, end - begin));
    }

    setCookies(cookies, expiration) {
        var expires = '';
        if (expiration) {
            var d = new Date();
            d.setTime(expiration);
            expires = "expires=" + d.toUTCString() + ';';
        }

        for (var key in cookies) {
            document.cookie = key + "=" + cookies[key] + ";" + expires + "path=/";

            if (key === 'token') {
                this.token = cookies[key];

                if(this.socket)
                    this.socket.close();
            }
        }
    }

    deleteOldCache() {
        try {
            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    var index = key.indexOf('_');

                    if (key.substr(0, index) !== this.token) {
                        localStorage.removeItem(key);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteAllCache() {
        try {
            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    localStorage.removeItem(key);
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteLocalCache(api_salt, data) {
        try {
            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.removeItem(this.token + '_' + api_salt);
            }
        } catch (e) {
            console.error(e);
        }
    }

    saveLocalCache(api_salt, data) {
        try {
            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.setItem(this.token + '_' + api_salt, JSON.stringify(data));
            }
        } catch (e) {
            console.error(e);
        }
    }

    getLocalCache(api_salt) {
        try {
            if (typeof (localStorage) == "object") {
                var localObject = localStorage.getItem(this.token + '_' + api_salt);
                try {
                    localObject = JSON.parse(localObject);

                    if(localObject === null && window.API_DATA && window.API_DATA[api_salt])
                    {
                        localObject = window.API_DATA[api_salt];
                    }

                    return localObject;
                } catch (e) {}
            }
        } catch (e) {
            console.error(e);
        }

        if(!window.API_DATA)
            return null;

        return window.API_DATA[api_salt] || null;
    }

    //---------------------------------------------------------

    emitSocketOpen() {
        const callbacks      = this.onOpenCallbacks;
        this.onOpenCallbacks = [];

        for (var key in callbacks) {
            try {
                callbacks[key].call(this, this);
            } catch (e) {
                console.error(e);
            }
        }

        var count = 0;
        for(var key in this.saltApiForcers) {
            count ++;
            this.saltApiForcers[key]();
        }
    }

    emitSocketClose() {
        if (this.isDev)
            console.warn('Socket closed');

        this.socketConnected = false;
        this.socket = false;
        this.fetchingApis = [];

        setTimeout(() => {
            console.log('Socket closed, reconnecting')
            this.socketConnect();
        }, 100);
    }

    emitSocketMessage(msg) {
        try {
            if (msg.type === 'message') {
                var data = msg.data;

                try {
                    data = JSON.parse(data);

                    if (data.api) {
                        this.emitApi(data.api, data.data, data.salt);
                    } else if (data.apiError) {
                        this.handleApiError(data.apiError, data.error, data.salt);
                    } else if (data.cookies) {
                        this.setCookies(data.cookies, data.expiration);
                    }
                } catch (e) {
                    console.error(e);
                }

                for (var key in this.socketHooks) {
                    try {
                        this.socketHooks[key](data);
                    } catch (e) {
                        console.error(e);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    emitApi(api, data, salt) {
        this.saveLocalCache(salt, data);
        var found = false;

        if (salt === undefined) {
            salt = this.getSalt(api, mergePost({}));
        }

        if(this.saltApiForcers[salt])
            delete this.saltApiForcers[salt];

        for (var key in this.onApiCallbacks) {
            try {

                const post = mergePost(JSON.parse(JSON.stringify(this.onApiCallbacks[key].post)));

                if (this.getSalt(this.onApiCallbacks[key].api, post) === salt)
                {
                    found = true;
                    this.onApiCallbacks[key].callback.call(this, data, api, this.onApiCallbacks[key].id);
                }
            } catch (e)
            {
                console.error(e);
            }
        }

        if (!found && this.isDev)
            console.warn('Api handler not found for', api, salt);
    }

    sendSocketMessage(msg) {
        if (!this.isClient)
            return null;

        if (typeof (msg) === 'object' || typeof (msg) === 'array') {
            try {
                msg = JSON.stringify(msg);
            } catch (e) {
                console.error(e);
            }
        }

        if (this.socketConnected === false) {
            return this.onSocketOpen(function () {
                this.socket.send(msg);
            });
        }

        this.socket.send(msg);
        return this;
    }

    onSocketOpen(cb) {
        if (this.socketConnected) {
            try {
                cb.call(this);
            } catch (e) {
                console.error(e);
            }
        } else {
            this.onOpenCallbacks.push(cb);
        }

        return this;
    }

    //---------------------------------------------------------

    sendApiAndForceResponse(api, data, salt) {
        const _this = this;

        this.saltApiForcers[salt] = function() {
            _this.sendSocketMessage({
                api:  api,
                data: data,
                salt: salt
            });
        }

        if(!this.socketConnected)
            return;

        this.saltApiForcers[salt]();
    }

    require(api, data, cb)
    {
        const res = super.require(api, data, cb);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);

        return res.id;
    }

    refresh(api, data)
    {
        const res = super.refresh(api, data);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);
    }

    post(api, data)
    {
        const _this = this;
        const res   = super.post(api, data);

        if(!res)
            return;

        return new Promise(function (resolve, reject) {

            _this.onApiCallbacks.push({
                id: res.id,
                api: api,
                post: res.data,
                //salt: salt,
                callback: function (res) {
                    _this.unbindListener(res.id);
                    resolve(res);
                }
            });

            _this.sendSocketMessage({
                api: api,
                data: res.data,
                salt: res.salt
            });
        });
    }
}

if (ApiManager.shared === undefined)
    ApiManager.shared = new ApiManager();

export default ApiManager.shared;<commit_msg>no message<commit_after>import BaseManager from '../common/apiManager.js';
import { mergePost } from '../common/init.js';
import ApiMerger     from '../common/merger.js';

const SockJS = require('sockjs-client');

class ApiManager extends BaseManager
{
    constructor()
    {
        super();
        this.isClient   = true;
        this.formPoster = __FormPosterReqPath;
        this.socketConnected = false;
        this.saltApiForcers  = {};

        if (!this.token)
            this.generateToken();
        else if (this.token.length < 10 || this.token.length > 20)
            this.generateToken();

        this.socketConnect();
        this.deleteOldCache();

        const _this = this;

        Vue.mixin({
            created()
            {
                try
                {
                    if(this.$options.preload && this.api)
                    {
                        const post     = _this.mergePost(this.api_data);
                        const api_salt = _this.getSalt(this.api, post);

                        if(window.API_DATA && window.API_DATA[api_salt])
                        {
                            ApiMerger(this, window.API_DATA[api_salt]);
                        }
                    }

                    if(this.api)
                    {
                        _this.bindVue(this, this.api, this.api_data);
                    }
                }
                catch(e)
                {
                    console.error(e);
                }
            }

        });
    }

    socketConnect()
    {
        if (!this.isClient)
            return null;

        if (this.socket) {
            try {
                console.warn('Closing old socket');
                this.socket.close();
            } catch (e) {
                console.error(e);
            }
        }

        var _this = this;
        const socket = new SockJS(this.base() + 'socketapi?token=' + this.token);
        this.socket = socket
        this.socket.onopen = function () {
            _this.socket = socket
            _this.socketConnected = true;
            _this.emitSocketOpen();
        };
        this.socket.onmessage = function (e) {
            _this.socketConnected = true;
            _this.emitSocketMessage(e);
        };
        this.socket.onclose = function () {
            _this.socketConnected = false;
            _this.emitSocketClose();
        };
    }

    base()
    {
        if (this.base_url)
            return this.base_url;

        var base = window.api_url || location.href;
        var index = base.indexOf('//');

        if (index != -1) {
            index = base.indexOf('/', index + 3);
            if (index != -1) {
                this.base_url = base.substr(0, index + 1);
                return this.base_url;
            }
        }

        return '';
    }

    //---------------------------------------------------------

    getCookie(name) {
        var dc = document.cookie;
        var prefix = name + "=";

        var begin = dc.indexOf("; " + prefix);
        var beginLength = 2 + prefix.length;

        if (begin == -1) {
            begin = dc.indexOf(";" + prefix);
            beginLength--;

            if (begin == -1 && dc.indexOf(prefix) == 0) {
                begin = 0;
                beginLength--;
            } else {
                return '';
            }
        }

        begin += beginLength;
        const end = dc.indexOf(';', begin);

        if (end == -1)
            return dc.substr(begin);

        return unescape(dc.substr(begin, end - begin));
    }

    setCookies(cookies, expiration) {
        var expires = '';
        if (expiration) {
            var d = new Date();
            d.setTime(expiration);
            expires = "expires=" + d.toUTCString() + ';';
        }

        for (var key in cookies) {
            document.cookie = key + "=" + cookies[key] + ";" + expires + "path=/";

            if (key === 'token') {
                this.token = cookies[key];

                if(this.socket)
                    this.socket.close();
            }
        }
    }

    deleteOldCache() {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    var index = key.indexOf('_');

                    if (key.substr(0, index) !== this.token) {
                        localStorage.removeItem(key);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteAllCache() {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                for (var i = localStorage.length - 1; i >= 0; i--) {
                    var key = localStorage.key(i);
                    localStorage.removeItem(key);
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    deleteLocalCache(api_salt, data) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.removeItem(this.token + '_' + api_salt);
            }
        } catch (e) {
            console.error(e);
        }
    }

    saveLocalCache(api_salt, data) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object" && localStorage !== undefined && localStorage !== null) {
                localStorage.setItem(this.token + '_' + api_salt, JSON.stringify(data));
            }
        } catch (e) {
            console.error(e);
        }
    }

    getLocalCache(api_salt) {
        try {
            if(location.href.indexOf('file://') === 0)
                return;

            if (typeof (localStorage) == "object") {
                var localObject = localStorage.getItem(this.token + '_' + api_salt);
                try {
                    localObject = JSON.parse(localObject);

                    if(localObject === null && window.API_DATA && window.API_DATA[api_salt])
                    {
                        localObject = window.API_DATA[api_salt];
                    }

                    return localObject;
                } catch (e) {}
            }
        } catch (e) {
            console.error(e);
        }

        if(!window.API_DATA)
            return null;

        return window.API_DATA[api_salt] || null;
    }

    //---------------------------------------------------------

    emitSocketOpen() {
        const callbacks      = this.onOpenCallbacks;
        this.onOpenCallbacks = [];

        for (var key in callbacks) {
            try {
                callbacks[key].call(this, this);
            } catch (e) {
                console.error(e);
            }
        }

        var count = 0;
        for(var key in this.saltApiForcers) {
            count ++;
            this.saltApiForcers[key]();
        }
    }

    emitSocketClose() {
        if (this.isDev)
            console.warn('Socket closed');

        this.socketConnected = false;
        this.socket = false;
        this.fetchingApis = [];

        setTimeout(() => {
            console.log('Socket closed, reconnecting')
            this.socketConnect();
        }, 100);
    }

    emitSocketMessage(msg) {
        try {
            if (msg.type === 'message') {
                var data = msg.data;

                try {
                    data = JSON.parse(data);

                    if (data.api) {
                        this.emitApi(data.api, data.data, data.salt);
                    } else if (data.apiError) {
                        this.handleApiError(data.apiError, data.error, data.salt);
                    } else if (data.cookies) {
                        this.setCookies(data.cookies, data.expiration);
                    }
                } catch (e) {
                    console.error(e);
                }

                for (var key in this.socketHooks) {
                    try {
                        this.socketHooks[key](data);
                    } catch (e) {
                        console.error(e);
                    }
                }
            }
        } catch (e) {
            console.error(e);
        }
    }

    emitApi(api, data, salt) {
        this.saveLocalCache(salt, data);
        var found = false;

        if (salt === undefined) {
            salt = this.getSalt(api, mergePost({}));
        }

        if(this.saltApiForcers[salt])
            delete this.saltApiForcers[salt];

        for (var key in this.onApiCallbacks) {
            try {

                const post = mergePost(JSON.parse(JSON.stringify(this.onApiCallbacks[key].post)));

                if (this.getSalt(this.onApiCallbacks[key].api, post) === salt)
                {
                    found = true;
                    this.onApiCallbacks[key].callback.call(this, data, api, this.onApiCallbacks[key].id);
                }
            } catch (e)
            {
                console.error(e);
            }
        }

        if (!found && this.isDev)
            console.warn('Api handler not found for', api, salt);
    }

    sendSocketMessage(msg) {
        if (!this.isClient)
            return null;

        if (typeof (msg) === 'object' || typeof (msg) === 'array') {
            try {
                msg = JSON.stringify(msg);
            } catch (e) {
                console.error(e);
            }
        }

        if (this.socketConnected === false) {
            return this.onSocketOpen(function () {
                this.socket.send(msg);
            });
        }

        this.socket.send(msg);
        return this;
    }

    onSocketOpen(cb) {
        if (this.socketConnected) {
            try {
                cb.call(this);
            } catch (e) {
                console.error(e);
            }
        } else {
            this.onOpenCallbacks.push(cb);
        }

        return this;
    }

    //---------------------------------------------------------

    sendApiAndForceResponse(api, data, salt) {
        const _this = this;

        this.saltApiForcers[salt] = function() {
            _this.sendSocketMessage({
                api:  api,
                data: data,
                salt: salt
            });
        }

        if(!this.socketConnected)
            return;

        this.saltApiForcers[salt]();
    }

    require(api, data, cb)
    {
        const res = super.require(api, data, cb);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);

        return res.id;
    }

    refresh(api, data)
    {
        const res = super.refresh(api, data);

        if(!res)
            return;

        this.sendApiAndForceResponse(api, res.data, res.salt);
    }

    post(api, data)
    {
        const _this = this;
        const res   = super.post(api, data);

        if(!res)
            return;

        return new Promise(function (resolve, reject) {

            _this.onApiCallbacks.push({
                id: res.id,
                api: api,
                post: res.data,
                //salt: salt,
                callback: function (res) {
                    _this.unbindListener(res.id);
                    resolve(res);
                }
            });

            _this.sendSocketMessage({
                api: api,
                data: res.data,
                salt: res.salt
            });
        });
    }
}

if (ApiManager.shared === undefined)
    ApiManager.shared = new ApiManager();

export default ApiManager.shared;<|endoftext|>


<commit_before>// copyright Jonathan Topf 2014

var Commands = {};

Commands.key_mapping = {
    'C' : 'A',
    'D' : 'S',
    'E' : 'D',
    'F' : 'F',
    'G' : 'G',
    'A' : 'H',
    'B' : 'J',
};

// ----------------------------------------------------------------------------------------------------
// BaseClass
// ----------------------------------------------------------------------------------------------------

Commands.BaseCommand = function (tag)
{
    this.completed = false;
    this.tag = tag;
    this.current_key = null;
    this.next_key = null;
    this.cycle = 0;
}

Commands.BaseCommand.updateWrapper = function (tick)
{
    this.update(tick);
    this.cycle ++;
}

Commands.BaseCommand.init = function ()

{

}

// ----------------------------------------------------------------------------------------------------
// Wait
// ----------------------------------------------------------------------------------------------------

Commands.Wait = function (tag, wait_object)
{
    Commands.BaseCommand.call(this, tag);
    this.wait_object = wait_object;
}

Commands.Wait.prototype = Object.create(Commands.BaseCommand)
Commands.Wait.prototype.constructor = Commands.Wait;

Commands.Wait.prototype.update = function ()
{
    this.completed = this.wait_object.completed;
}

// ----------------------------------------------------------------------------------------------------
// Sleep
// ----------------------------------------------------------------------------------------------------

Commands.Sleep = function (tag, time)
{
    Commands.BaseCommand.call(this, tag);
    this.timer = time;
}

Commands.Sleep.prototype = Object.create(Commands.BaseCommand)
Commands.Sleep.prototype.constructor = Commands.Sleep;

Commands.Sleep.prototype.update = function ()
{
    if (this.timer <= 0)
    {
        this.completed = true;
    } else {
        this.timer -= 1 / 60;
    }
}

// ----------------------------------------------------------------------------------------------------
// Lights
// ----------------------------------------------------------------------------------------------------

Commands.Lights = function (tag, light, args) // args { value, speed }
{
    Commands.BaseCommand.call(this, tag);
    this.light = light;
    this.args = args;
}

Commands.Lights.prototype = Object.create(Commands.BaseCommand)
Commands.Lights.prototype.constructor = Commands.Lights;

Commands.Lights.prototype.update = function ()
{
    this.light.target_intensity = this.args.value;
    this.light.speed = this.args.speed;
    this.completed = true;
}

// ----------------------------------------------------------------------------------------------------
// Speak
// ----------------------------------------------------------------------------------------------------

Commands.Speak = function (tag, text, buffer, singer, args)
{
    Commands.BaseCommand.call(this, tag);
    this.text = text;
    this.args = args;
    this.buffer = buffer;
    this.singer = singer;
    this.mouth_phase = 0;
    this.mouth_freq = 3;
}

Commands.Speak.prototype = Object.create(Commands.BaseCommand)
Commands.Speak.prototype.constructor = Commands.Speak;

Commands.Speak.prototype.update = function ()
{
    if (this.mouth_phase == 0)
    {
        this.singer.talk();
    }

    this.mouth_phase ++;

    if (this.mouth_phase == this.mouth_freq)
    {
        this.mouth_phase = 0;
    }

    if (this.buffer.busy == false){
        this.completed = true;
        this.singer.talk('smile');
    }
}

Commands.Speak.prototype.init = function ()
{
    this.buffer.append(this.text, this.args);
}

// ----------------------------------------------------------------------------------------------------
// SongDemo
// ----------------------------------------------------------------------------------------------------

Commands.SongDemo = function (tag, song, singer, bpm)
{
    Commands.BaseCommand.call(this, tag);
    this.song = song;
    this.singer = singer;
    this.ticks_per_mesure = Math.round(bpm / 16); // 16 mesures per bar

    this.init();
}

Commands.SongDemo.prototype = Object.create(Commands.BaseCommand)
Commands.SongDemo.prototype.constructor = Commands.SongDemo;

Commands.SongDemo.prototype.update = function ()
{
    if (this.mesure_phase == 0)
    {
        this.mesure_phase = this.ticks_per_mesure;
        this.mesure ++;

        var singing = false

        for (var i = 0; i < Sound.scale.length; i ++)
        {
            if (this.song[this.mesure][i] == 1)
            {
                Sound.synth[Sound.scale[i]].play();
                this.singer.demoKey(Commands.key_mapping[Sound.scale[i]]);
                singing = true;
            } else {
                Sound.synth[Sound.scale[i]].pause();
            }
        }

        if (singing)
        {
            this.singer.talk('ooo');
        } else {
            this.singer.talk('smile');
        }
    }

    if (this.song.length == this.mesure + 1)
    {
        this.completed = true;
    }

    this.mesure_phase --;
}

Commands.SongDemo.prototype.init = function ()
{
    this.mesure_phase = 0;
    this.mesure = 0;
    this.song_step = 0;
}

// ----------------------------------------------------------------------------------------------------
// ABSwitch
// ----------------------------------------------------------------------------------------------------

Commands.ABSwitch = function (tag, buffer, a_text, a_key, b_text, b_key)
{
    Commands.BaseCommand.call(this, tag);
    this.buffer = buffer;

    this.a_text = a_text;
    this.a_key = a_key;
    this.b_text = b_text;
    this.b_key = b_key;
}

Commands.ABSwitch.prototype = Object.create(Commands.BaseCommand)
Commands.ABSwitch.prototype.constructor = Commands.ABSwitch;

Commands.ABSwitch.prototype.update = function ()
{
    if (this.cycle == 1) {
        this.b_arrow.hide();
    }

    if (Keyboard.status['up'] == 1)
    {
        this.a_arrow.show();
        this.b_arrow.hide();
        this.next_key = Logic.script_key_pairs[this.a_key];
    } else if (Keyboard.status['down'] == 1)
    {
        this.a_arrow.hide();
        this.b_arrow.show();
        this.next_key = Logic.script_key_pairs[this.b_key];
    } else if (Keyboard.status['enter'] == 1)
    {
        this.completed = true;
    }
}

Commands.ABSwitch.prototype.init = function() {
    var args = {visible : true};
    this.buffer.init();
    this.buffer.append(this.a_text,  args);
    this.buffer.append(' *', args);
    this.a_arrow = this.buffer.chars[this.buffer.chars.length -1];
    this.buffer.append('\n', args);
    this.buffer.append(this.b_text, args);
    this.buffer.append(' *', args);
    this.b_arrow = this.buffer.chars[this.buffer.chars.length -1];
    this.buffer.busy = false;
    this.b_arrow.hide();
    this.next_key = Logic.script_key_pairs[this.a_key];
}

// ----------------------------------------------------------------------------------------------------
// SongTest
// ----------------------------------------------------------------------------------------------------

Commands.SongTest = function (tag, buffer, song, singer, success_tag, fail_tag)
{
    Commands.BaseCommand.call(this, tag);
    this.buffer = buffer;
    this.song = song;
    this.singer = singer;

    this.success_tag = success_tag;
    this.fail_tag = fail_tag;

    this.current_note = null;

    this.note_stack = [];

}

Commands.SongTest.prototype = Object.create(Commands.BaseCommand)
Commands.SongTest.prototype.constructor = Commands.SongTest;

Commands.SongTest.prototype.update = function ()
{
    // update stack
    for (var i = 0; i < Sound.scale.length; i ++)
    {
        if (Keyboard.status[Commands.key_mapping[Sound.scale[i]]] > 0)
        {
            this.pushNote(Sound.scale[i]);
        } else {
            this.popNote(Sound.scale[i]);
        }
    }

    // play and stop sounds
    for (var i = 0; i < Sound.scale.length; i ++)
    {
        if (this.note_stack.length > 0)
        {
            if (this.note_stack[this.note_stack.length -1] != this.current_note) { // note has changed
                // sounds
                Sound.stopAll();
                this.current_note = this.note_stack[this.note_stack.length -1] ;
                Sound.synth[this.current_note].play();
                // singer
                this.singer.sing();
            }
        } else {
            Sound.stopAll()
            this.singer.talk('smile');
            this.current_note = null;
        }
    }

    var line_string = '';
    for (var i = 0; i < this.song[0].length; i++)
    {
        if (this.song[this.current_song_step][i] == undefined)
        {
            line_string += '-';
        } else {
            line_string += '1';
        }
        line_string += ',';
    }
    console.log(line_string);

}

Commands.SongTest.prototype.init = function ()
{
    this.current_song_step = 0;
}

Commands.SongTest.prototype.advanceStep = function ()
{
    while (true)
    {
        this.current_song_step ++;
        for (var i = 0; i < Sound.scale.length; i++)
        {
            if (this.song[this.current_song_step][i] != this.song[this.current_song_step - 1][i])
            {
                return;
            }
        }
    }
}

Commands.SongTest.prototype.pushNote = function (note)
{
    for (var i = 0; i < this.note_stack.length; i++)
    {
        if (this.note_stack[i] == note)
        {
            return;
        }
    }

    this.note_stack.push(note);
}

Commands.SongTest.prototype.popNote = function (note)
{
    for (var i = 0; i < this.note_stack.length; i++)
    {
        if (this.note_stack[i] == note)
        {
            this.note_stack.splice(i, 1);
            return;
        }
    }

}

Commands.SongTest.prototype.compareSongToKeys = function ()
{
    for (var i = 0; i < Sound.scale.length; i++)
    {
        var is_top_note = this.isTopNote(Sound.scale[i]);

        if (this.song[this.current_song_step] != 1 & is_top_note | this.song[this.current_song_step] == undefined & is_top_note)
        {
            return false;
        }
    }
    return true;
}

Commands.SongTest.prototype.isTopNote = function (note)
{
    if (this.note_stack[this.note_stack.length - 1] == note)
        {
            return true;
        }
    return false;
}

// ----------------------------------------------------------------------------------------------------
// ClearBuffer
// ----------------------------------------------------------------------------------------------------

Commands.ClearBuffer = function (tag, buffer)
{
    Commands.BaseCommand.call(this, tag);
    this.buffer = buffer;
}

Commands.ClearBuffer.prototype = Object.create(Commands.BaseCommand)
Commands.ClearBuffer.prototype.constructor = Commands.ClearBuffer;

Commands.ClearBuffer.prototype.update = function ()
{
    this.buffer.init();
    this.completed = true;
}

// ----------------------------------------------------------------------------------------------------
// End
// ----------------------------------------------------------------------------------------------------

Commands.End = function (tag)
{
    Commands.BaseCommand.call(this, tag);
}

Commands.End.prototype = Object.create(Commands.BaseCommand)
Commands.End.prototype.constructor = Commands.End;

Commands.End.prototype.update = function ()
{

}
<commit_msg>fixed bug in songDemo causing first note to be omitted<commit_after>// copyright Jonathan Topf 2014

var Commands = {};

Commands.key_mapping = {
    'C' : 'A',
    'D' : 'S',
    'E' : 'D',
    'F' : 'F',
    'G' : 'G',
    'A' : 'H',
    'B' : 'J',
};

// ----------------------------------------------------------------------------------------------------
// BaseClass
// ----------------------------------------------------------------------------------------------------

Commands.BaseCommand = function (tag)
{
    this.completed = false;
    this.tag = tag;
    this.current_key = null;
    this.next_key = null;
    this.cycle = 0;
}

Commands.BaseCommand.updateWrapper = function (tick)
{
    this.update(tick);
    this.cycle ++;
}

Commands.BaseCommand.init = function ()

{

}

// ----------------------------------------------------------------------------------------------------
// Wait
// ----------------------------------------------------------------------------------------------------

Commands.Wait = function (tag, wait_object)
{
    Commands.BaseCommand.call(this, tag);
    this.wait_object = wait_object;
}

Commands.Wait.prototype = Object.create(Commands.BaseCommand)
Commands.Wait.prototype.constructor = Commands.Wait;

Commands.Wait.prototype.update = function ()
{
    this.completed = this.wait_object.completed;
}

// ----------------------------------------------------------------------------------------------------
// Sleep
// ----------------------------------------------------------------------------------------------------

Commands.Sleep = function (tag, time)
{
    Commands.BaseCommand.call(this, tag);
    this.timer = time;
}

Commands.Sleep.prototype = Object.create(Commands.BaseCommand)
Commands.Sleep.prototype.constructor = Commands.Sleep;

Commands.Sleep.prototype.update = function ()
{
    if (this.timer <= 0)
    {
        this.completed = true;
    } else {
        this.timer -= 1 / 60;
    }
}

// ----------------------------------------------------------------------------------------------------
// Lights
// ----------------------------------------------------------------------------------------------------

Commands.Lights = function (tag, light, args) // args { value, speed }
{
    Commands.BaseCommand.call(this, tag);
    this.light = light;
    this.args = args;
}

Commands.Lights.prototype = Object.create(Commands.BaseCommand)
Commands.Lights.prototype.constructor = Commands.Lights;

Commands.Lights.prototype.update = function ()
{
    this.light.target_intensity = this.args.value;
    this.light.speed = this.args.speed;
    this.completed = true;
}

// ----------------------------------------------------------------------------------------------------
// Speak
// ----------------------------------------------------------------------------------------------------

Commands.Speak = function (tag, text, buffer, singer, args)
{
    Commands.BaseCommand.call(this, tag);
    this.text = text;
    this.args = args;
    this.buffer = buffer;
    this.singer = singer;
    this.mouth_phase = 0;
    this.mouth_freq = 3;
}

Commands.Speak.prototype = Object.create(Commands.BaseCommand)
Commands.Speak.prototype.constructor = Commands.Speak;

Commands.Speak.prototype.update = function ()
{
    if (this.mouth_phase == 0)
    {
        this.singer.talk();
    }

    this.mouth_phase ++;

    if (this.mouth_phase == this.mouth_freq)
    {
        this.mouth_phase = 0;
    }

    if (this.buffer.busy == false){
        this.completed = true;
        this.singer.talk('smile');
    }
}

Commands.Speak.prototype.init = function ()
{
    this.buffer.append(this.text, this.args);
}

// ----------------------------------------------------------------------------------------------------
// SongDemo
// ----------------------------------------------------------------------------------------------------

Commands.SongDemo = function (tag, song, singer, bpm)
{
    Commands.BaseCommand.call(this, tag);
    this.song = song;
    this.singer = singer;
    this.ticks_per_mesure = Math.round(bpm / 16); // 16 mesures per bar

    this.init();
}

Commands.SongDemo.prototype = Object.create(Commands.BaseCommand)
Commands.SongDemo.prototype.constructor = Commands.SongDemo;

Commands.SongDemo.prototype.update = function ()
{
    if (this.mesure_phase == 0)
    {
        this.mesure_phase = this.ticks_per_mesure;

        var singing = false

        for (var i = 0; i < Sound.scale.length; i ++)
        {
            if (this.song[this.mesure][i] == 1)
            {
                Sound.synth[Sound.scale[i]].play();
                this.singer.demoKey(Commands.key_mapping[Sound.scale[i]]);
                singing = true;
            } else {
                Sound.synth[Sound.scale[i]].pause();
            }
        }

        this.mesure ++;

        if (singing)
        {
            this.singer.talk('ooo');
        } else {
            this.singer.talk('smile');
        }
    }

    if (this.song.length == this.mesure)
    {
        this.completed = true;
    }

    this.mesure_phase --;
}

Commands.SongDemo.prototype.init = function ()
{
    this.mesure_phase = 0;
    this.mesure = 0;
    this.song_step = 0;
}

// ----------------------------------------------------------------------------------------------------
// ABSwitch
// ----------------------------------------------------------------------------------------------------

Commands.ABSwitch = function (tag, buffer, a_text, a_key, b_text, b_key)
{
    Commands.BaseCommand.call(this, tag);
    this.buffer = buffer;

    this.a_text = a_text;
    this.a_key = a_key;
    this.b_text = b_text;
    this.b_key = b_key;
}

Commands.ABSwitch.prototype = Object.create(Commands.BaseCommand)
Commands.ABSwitch.prototype.constructor = Commands.ABSwitch;

Commands.ABSwitch.prototype.update = function ()
{
    if (this.cycle == 1) {
        this.b_arrow.hide();
    }

    if (Keyboard.status['up'] == 1)
    {
        this.a_arrow.show();
        this.b_arrow.hide();
        this.next_key = Logic.script_key_pairs[this.a_key];
    } else if (Keyboard.status['down'] == 1)
    {
        this.a_arrow.hide();
        this.b_arrow.show();
        this.next_key = Logic.script_key_pairs[this.b_key];
    } else if (Keyboard.status['enter'] == 1)
    {
        this.completed = true;
    }
}

Commands.ABSwitch.prototype.init = function() {
    var args = {visible : true};
    this.buffer.init();
    this.buffer.append(this.a_text,  args);
    this.buffer.append(' *', args);
    this.a_arrow = this.buffer.chars[this.buffer.chars.length -1];
    this.buffer.append('\n', args);
    this.buffer.append(this.b_text, args);
    this.buffer.append(' *', args);
    this.b_arrow = this.buffer.chars[this.buffer.chars.length -1];
    this.buffer.busy = false;
    this.b_arrow.hide();
    this.next_key = Logic.script_key_pairs[this.a_key];
}

// ----------------------------------------------------------------------------------------------------
// SongTest
// ----------------------------------------------------------------------------------------------------

Commands.SongTest = function (tag, buffer, song, singer, success_tag, fail_tag)
{
    Commands.BaseCommand.call(this, tag);
    this.buffer = buffer;
    this.song = song;
    this.singer = singer;

    this.success_tag = success_tag;
    this.fail_tag = fail_tag;

    this.current_note = null;

    this.note_stack = [];

}

Commands.SongTest.prototype = Object.create(Commands.BaseCommand)
Commands.SongTest.prototype.constructor = Commands.SongTest;

Commands.SongTest.prototype.update = function ()
{
    // update stack
    for (var i = 0; i < Sound.scale.length; i ++)
    {
        if (Keyboard.status[Commands.key_mapping[Sound.scale[i]]] > 0)
        {
            this.pushNote(Sound.scale[i]);
        } else {
            this.popNote(Sound.scale[i]);
        }
    }

    // play and stop sounds
    for (var i = 0; i < Sound.scale.length; i ++)
    {
        if (this.note_stack.length > 0)
        {
            if (this.note_stack[this.note_stack.length -1] != this.current_note) { // note has changed
                // sounds
                Sound.stopAll();
                this.current_note = this.note_stack[this.note_stack.length -1] ;
                Sound.synth[this.current_note].play();
                // singer
                this.singer.sing();
            }
        } else {
            Sound.stopAll()
            this.singer.talk('smile');
            this.current_note = null;
        }
    }

    var line_string = '';
    for (var i = 0; i < this.song[0].length; i++)
    {
        if (this.song[this.current_song_step][i] == undefined)
        {
            line_string += '-';
        } else {
            line_string += '1';
        }
        line_string += ',';
    }
    console.log(line_string);

}

Commands.SongTest.prototype.init = function ()
{
    this.current_song_step = 0;
}

Commands.SongTest.prototype.advanceStep = function ()
{
    while (true)
    {
        this.current_song_step ++;
        for (var i = 0; i < Sound.scale.length; i++)
        {
            if (this.song[this.current_song_step][i] != this.song[this.current_song_step - 1][i])
            {
                return;
            }
        }
    }
}

Commands.SongTest.prototype.pushNote = function (note)
{
    for (var i = 0; i < this.note_stack.length; i++)
    {
        if (this.note_stack[i] == note)
        {
            return;
        }
    }

    this.note_stack.push(note);
}

Commands.SongTest.prototype.popNote = function (note)
{
    for (var i = 0; i < this.note_stack.length; i++)
    {
        if (this.note_stack[i] == note)
        {
            this.note_stack.splice(i, 1);
            return;
        }
    }

}

Commands.SongTest.prototype.compareSongToKeys = function ()
{
    for (var i = 0; i < Sound.scale.length; i++)
    {
        var is_top_note = this.isTopNote(Sound.scale[i]);

        if (this.song[this.current_song_step] != 1 & is_top_note | this.song[this.current_song_step] == undefined & is_top_note)
        {
            return false;
        }
    }
    return true;
}

Commands.SongTest.prototype.isTopNote = function (note)
{
    if (this.note_stack[this.note_stack.length - 1] == note)
        {
            return true;
        }
    return false;
}

// ----------------------------------------------------------------------------------------------------
// ClearBuffer
// ----------------------------------------------------------------------------------------------------

Commands.ClearBuffer = function (tag, buffer)
{
    Commands.BaseCommand.call(this, tag);
    this.buffer = buffer;
}

Commands.ClearBuffer.prototype = Object.create(Commands.BaseCommand)
Commands.ClearBuffer.prototype.constructor = Commands.ClearBuffer;

Commands.ClearBuffer.prototype.update = function ()
{
    this.buffer.init();
    this.completed = true;
}

// ----------------------------------------------------------------------------------------------------
// End
// ----------------------------------------------------------------------------------------------------

Commands.End = function (tag)
{
    Commands.BaseCommand.call(this, tag);
}

Commands.End.prototype = Object.create(Commands.BaseCommand)
Commands.End.prototype.constructor = Commands.End;

Commands.End.prototype.update = function ()
{

}
<|endoftext|>


<commit_before>import {
  message,
  addText,
  addButtonTemplate,
  addGenericTemplate,
  addQuickReplies,
  addShareLocation,
  addImage
} from './fb-message-builder';

import {
  messageIs,
  hasPostback,
  userSays,
  getUserMessage
} from './fb-message-parser'

import {getVenues} from './smartum-api'

const sessions = {};
const getSession = (userId) => {
  if (!sessions[userId]) {
    sessions[userId] = {};
  }
  return sessions[userId];
};

const resetSession = (userId) => {
  sessions[userId] = {};
};

export default function storyRunner(sendMessage, getContextForUser = getSession, resetContextForUser = resetSession, getCurrentDate = () => new Date()) {
  return function runStory(messagingEvent) {

    const newMessage = () => {
      return message(messagingEvent.sender.id);
    };

    const greet = () => {
      return newMessage()
        .then(addText('Hi there!'))
        .then(addQuickReplies(['Let\'s start!']))
        .then(sendMessage);
    };

    const unknownCommand = () => {
      return newMessage()
        .then(addText('Dunno'))
        .then(sendMessage);
    };

    const askLocation = () => {
      return newMessage()
        .then(addText('What is your location'))
        .then(sendMessage)
        .then(newMessage)
        .then(addGenericTemplate([{
          title: 'Share location',
          subtitle: 'Pro tip: You can also write the starting point if it\'s not your current location'
        }]))
        .then(addShareLocation())
        .then(sendMessage);
    };

    const recommendPlaces = (context) => {
      return newMessage()
        .then(getVenues(context.location.lat, context.location.lon))
        .then(restaurants => {
          return addGenericTemplate(restaurants.map(restaurant => {
            return {
              title: restaurant.name,
              subtitle: restaurant.post_address
            }
          }))
        });
    }

    const userSharesLocation = () => messagingEvent.message && messagingEvent.message.attachments;

    const context = getContextForUser(messagingEvent.sender.id);

    if (userSays('hi', 'hello', 'get started', 'help', 'Get started')) {
      Object.keys(context).forEach(key => delete context[key]);
      context.greeted = true;
    }
    else if (userSays('Let\'s start!', 'Start')) context.started = true;
    else if (userSharesLocation()) context.location = getUserMessage();
    else if (userSays('reset')) resetContextForUser();
    else return unknownCommand();

    if (context.greeted && !context.started) return greet();
    else if (context.started && !context.location) return askLocation()
    else if (context.started && context.location) return recommendPlaces(context)
  };
}
<commit_msg>Rolled back to working code<commit_after>import {
  message,
  addText,
  addButtonTemplate,
  addGenericTemplate,
  addQuickReplies,
  addShareLocation,
  addImage
} from './fb-message-builder';

import {getVenues} from './smartum-api'

const sessions = {};
const getSession = (userId) => {
  if (!sessions[userId]) {
    sessions[userId] = {};
  }
  return sessions[userId];
};

const resetSession = (userId) => {
  sessions[userId] = {};
};

export default function storyRunner(sendMessage, getContextForUser = getSession, resetContextForUser = resetSession, getCurrentDate = () => new Date()) {
  return function runStory(messagingEvent) {

    const messageIs = text => {
      if (messagingEvent.message && messagingEvent.message.text) {
        return messagingEvent.message.text.toLowerCase() === text.toLowerCase();
      }
    };

    const hasPostback = payload => {
      return messagingEvent.postback && messagingEvent.postback.payload === payload;
    };

    const userSays = (...keywords) => keywords.some(word => messageIs(word) || hasPostback(word));

    const getUserMessage = () => {
      if (messagingEvent.message && messagingEvent.message.text) {
        return messagingEvent.message.text.toLowerCase();
      }

      if (messagingEvent.postback) {
        return messagingEvent.postback.payload.toLowerCase();
      }

      if (messagingEvent.message && messagingEvent.message.attachments) {
        const locationAttachment = messagingEvent.message.attachments[0];
        return locationAttachment.payload.coordinates;
      }
    };

    const newMessage = () => {
      return message(messagingEvent.sender.id);
    };

    const greet = () => {
      return newMessage()
        .then(addText('Hi there!'))
        .then(addQuickReplies(['Let\'s start!']))
        .then(sendMessage);
    };

    const unknownCommand = () => {
      return newMessage()
        .then(addText('Dunno'))
        .then(sendMessage);
    };

    const askLocation = () => {
      return newMessage()
        .then(addText('What is your location'))
        .then(sendMessage)
        .then(newMessage)
        .then(addGenericTemplate([{
          title: 'Share location',
          subtitle: 'Pro tip: You can also write the starting point if it\'s not your current location'
        }]))
        .then(addShareLocation())
        .then(sendMessage);
    };

    const recommendPlaces = (context) => {
      return newMessage()
        .then(getVenues(context.location.lat, context.location.lon))
        .then(restaurants => {
          return addGenericTemplate(restaurants.map(restaurant => {
            return {
              title: restaurant.name,
              subtitle: restaurant.post_address
            }
          }))
        });
    }

    const userSharesLocation = () => messagingEvent.message && messagingEvent.message.attachments;

    const context = getContextForUser(messagingEvent.sender.id);

    if (userSays('hi', 'hello', 'get started', 'help', 'Get started')) {
      Object.keys(context).forEach(key => delete context[key]);
      context.greeted = true;
    }
    else if (userSays('Let\'s start!', 'Start')) context.started = true;
    else if (userSharesLocation()) context.location = getUserMessage();
    else if (userSays('reset')) resetContextForUser();
    else return unknownCommand();

    if (context.greeted && !context.started) return greet();
    else if (context.started && !context.location) return askLocation()
    else if (context.started && context.location) return recommendPlaces(context)
  };
}
<|endoftext|>


<commit_before>import _ from 'lodash';
import supports from 'ui/utils/supports';
import FilterBarPushFilterProvider from 'ui/filter_bar/push_filter';

define(function (require) {
  require('ui/registry/vis_types').register(EnhancedTileMapVisProvider);
  require('plugins/enhanced_tilemap/vis.less');
  require('plugins/enhanced_tilemap/visController');

  function EnhancedTileMapVisProvider(Private, getAppState, courier, config) {
    var TemplateVisType = Private(require('ui/template_vis_type/TemplateVisType'));
    var Schemas = Private(require('ui/Vis/Schemas'));

    return new TemplateVisType({
      name: 'enhanced_tilemap',
      title: 'Enhanced Tile map',
      icon: 'fa-map-marker',
      description: 'Enhanced tile map',
      template: require('plugins/enhanced_tilemap/vis.html'),
      params: {
        defaults: {
          mapType: 'Scaled Circle Markers',
          isDesaturated: true,
          addTooltip: true,
          heatMaxZoom: 16,
          heatMinOpacity: 0.1,
          heatRadius: 25,
          heatBlur: 15,
          heatNormalizeData: true,
          mapZoom: 2,
          mapCenter: [15, 5],
          wms: config.get('visualization:tileMap:WMSdefaults')
        },
        mapTypes: ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid', 'Heatmap'],
        canDesaturate: !!supports.cssFilters,
        editor: require('plugins/enhanced_tilemap/options.html')
      },
      schemas: new Schemas([
        {
          group: 'metrics',
          name: 'metric',
          title: 'Value',
          min: 1,
          max: 1,
          aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'],
          defaults: [
            { schema: 'metric', type: 'count' }
          ]
        },
        {
          group: 'buckets',
          name: 'segment',
          title: 'Geo Coordinates',
          aggFilter: 'geohash_grid',
          min: 1,
          max: 1
        },
        {
          group: 'buckets',
          name: 'split',
          title: 'Split Chart',
          deprecate: true,
          deprecateMessage: 'The Split Chart feature for Tile Maps has been deprecated.',
          min: 0,
          max: 1
        }
      ])
    });
  }

  return EnhancedTileMapVisProvider;
});<commit_msg>remove split chart<commit_after>import _ from 'lodash';
import supports from 'ui/utils/supports';
import FilterBarPushFilterProvider from 'ui/filter_bar/push_filter';

define(function (require) {
  require('ui/registry/vis_types').register(EnhancedTileMapVisProvider);
  require('plugins/enhanced_tilemap/vis.less');
  require('plugins/enhanced_tilemap/visController');

  function EnhancedTileMapVisProvider(Private, getAppState, courier, config) {
    var TemplateVisType = Private(require('ui/template_vis_type/TemplateVisType'));
    var Schemas = Private(require('ui/Vis/Schemas'));

    return new TemplateVisType({
      name: 'enhanced_tilemap',
      title: 'Enhanced Tile map',
      icon: 'fa-map-marker',
      description: 'Enhanced tile map',
      template: require('plugins/enhanced_tilemap/vis.html'),
      params: {
        defaults: {
          mapType: 'Scaled Circle Markers',
          isDesaturated: true,
          addTooltip: true,
          heatMaxZoom: 16,
          heatMinOpacity: 0.1,
          heatRadius: 25,
          heatBlur: 15,
          heatNormalizeData: true,
          mapZoom: 2,
          mapCenter: [15, 5],
          wms: config.get('visualization:tileMap:WMSdefaults')
        },
        mapTypes: ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid', 'Heatmap'],
        canDesaturate: !!supports.cssFilters,
        editor: require('plugins/enhanced_tilemap/options.html')
      },
      schemas: new Schemas([
        {
          group: 'metrics',
          name: 'metric',
          title: 'Value',
          min: 1,
          max: 1,
          aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'],
          defaults: [
            { schema: 'metric', type: 'count' }
          ]
        },
        {
          group: 'buckets',
          name: 'segment',
          title: 'Geo Coordinates',
          aggFilter: 'geohash_grid',
          min: 1,
          max: 1
        }
      ])
    });
  }

  return EnhancedTileMapVisProvider;
});<|endoftext|>


<commit_before>import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
import { find, get, keyBy, mapValues } from 'lodash';

import { PAYMENT_METHOD_SERVICE, PAYMENT_METHOD_TYPE } from '../../../constants/paymentMethods';
import models, { Op, sequelize } from '../../../models';
import { PayoutMethodTypes } from '../../../models/PayoutMethod';
import TransferwiseLib from '../../../paymentProviders/transferwise';
import { Unauthorized } from '../../errors';
import { HostApplicationCollection } from '../collection/HostApplicationCollection';
import { PaymentMethodType, PayoutMethodType } from '../enum';
import { ChronologicalOrderInput } from '../input/ChronologicalOrderInput';
import { Account, AccountFields } from '../interface/Account';
import { AccountWithContributions, AccountWithContributionsFields } from '../interface/AccountWithContributions';
import { CollectionArgs } from '../interface/Collection';
import URL from '../scalar/URL';

import { Amount } from './Amount';
import { HostMetrics } from './HostMetrics';
import { HostPlan } from './HostPlan';
import { PaymentMethod } from './PaymentMethod';
import PayoutMethod from './PayoutMethod';

export const Host = new GraphQLObjectType({
  name: 'Host',
  description: 'This represents an Host account',
  interfaces: () => [Account, AccountWithContributions],
  fields: () => {
    return {
      ...AccountFields,
      ...AccountWithContributionsFields,
      hostFeePercent: {
        type: GraphQLInt,
        resolve(collective) {
          return collective.hostFeePercent;
        },
      },
      totalHostedCollectives: {
        type: GraphQLInt,
        resolve(collective) {
          return collective.getHostedCollectivesCount();
        },
      },
      isOpenToApplications: {
        type: GraphQLBoolean,
        resolve(collective) {
          return collective.canApply();
        },
      },
      termsUrl: {
        type: URL,
        resolve(collective) {
          return get(collective, 'settings.tos');
        },
      },
      plan: {
        type: new GraphQLNonNull(HostPlan),
        resolve(host) {
          return host.getPlan();
        },
      },
      hostMetrics: {
        type: new GraphQLNonNull(HostMetrics),
        args: {
          from: {
            type: GraphQLString,
            description: "Inferior date limit in which we're calculating the metrics",
          },
          to: {
            type: GraphQLString,
            description: "Superior date limit in which we're calculating the metrics",
          },
        },
        async resolve(host, args) {
          const metrics = await host.getHostMetrics(args?.from, args?.to);
          const toAmount = value => ({ value, currency: host.currency });
          return mapValues(metrics, (value, key) => (key.includes('Percent') ? value : toAmount(value)));
        },
      },
      supportedPaymentMethods: {
        type: new GraphQLList(PaymentMethodType),
        description:
          'The list of payment methods (Stripe, Paypal, manual bank transfer, etc ...) the Host can accept for its Collectives',
        async resolve(collective, _, req) {
          const supportedPaymentMethods = [];

          // Paypal, Stripe = connected accounts
          const connectedAccounts = await req.loaders.Collective.connectedAccounts.load(collective.id);

          if (find(connectedAccounts, ['service', 'stripe'])) {
            supportedPaymentMethods.push('CREDIT_CARD');
          }

          if (find(connectedAccounts, ['service', 'paypal']) && !collective.settings?.disablePaypalDonations) {
            supportedPaymentMethods.push('PAYPAL');
          }

          // bank transfer = manual in host settings
          if (get(collective, 'settings.paymentMethods.manual', null)) {
            supportedPaymentMethods.push('BANK_TRANSFER');
          }

          return supportedPaymentMethods;
        },
      },
      bankAccount: {
        type: PayoutMethod,
        async resolve(collective, _, req) {
          const payoutMethods = await req.loaders.PayoutMethod.byCollectiveId.load(collective.id);
          return payoutMethods.find(c => c.type === 'BANK_ACCOUNT' && c.data?.isManualBankTransfer);
        },
      },
      paypalPreApproval: {
        type: PaymentMethod,
        description: 'Paypal preapproval info. Returns null if PayPal account is not connected.',
        resolve: async host => {
          return models.PaymentMethod.findOne({
            where: {
              CollectiveId: host.id,
              service: PAYMENT_METHOD_SERVICE.PAYPAL,
              type: PAYMENT_METHOD_TYPE.ADAPTIVE,
            },
          });
        },
      },
      supportedPayoutMethods: {
        type: new GraphQLList(PayoutMethodType),
        description: 'The list of payout methods this Host accepts for its expenses',
        async resolve(collective, _, req) {
          const connectedAccounts = await req.loaders.Collective.connectedAccounts.load(collective.id);
          const supportedPayoutMethods = [PayoutMethodTypes.OTHER, PayoutMethodTypes.ACCOUNT_BALANCE];
          if (connectedAccounts?.find?.(c => c.service === 'transferwise')) {
            supportedPayoutMethods.push(PayoutMethodTypes.BANK_ACCOUNT);
          }
          if (connectedAccounts?.find?.(c => c.service === 'paypal') || !collective.settings?.disablePaypalPayouts) {
            supportedPayoutMethods.push(PayoutMethodTypes.PAYPAL);
          }

          return supportedPayoutMethods;
        },
      },
      transferwiseBalances: {
        type: new GraphQLList(Amount),
        description: 'Transferwise balances. Returns null if Transferwise account is not connected.',
        resolve: async host => {
          const transferwiseAccount = await models.ConnectedAccount.findOne({
            where: { CollectiveId: host.id, service: 'transferwise' },
          });

          if (transferwiseAccount) {
            return TransferwiseLib.getAccountBalances(transferwiseAccount).then(balances => {
              return balances.map(balance => ({
                value: Math.round(balance.amount.value * 100),
                currency: balance.amount.currency,
              }));
            });
          }
        },
      },
      pendingApplications: {
        type: new GraphQLNonNull(HostApplicationCollection),
        description: 'Pending applications for this host',
        args: {
          ...CollectionArgs,
          searchTerm: {
            type: GraphQLString,
            description:
              'A term to search membership. Searches in collective tags, name, slug, members description and role.',
          },
          orderBy: {
            type: new GraphQLNonNull(ChronologicalOrderInput),
            defaultValue: { field: 'createdAt', direction: 'DESC' },
            description: 'Order of the results',
          },
        },
        resolve: async (host, args, req) => {
          if (!req.remoteUser?.isAdmin(host.id)) {
            throw new Unauthorized('You need to be logged in as an admin of the host to see its pending application');
          }

          const where = { HostCollectiveId: host.id, approvedAt: null };
          const sanitizedSearch = args.searchTerm?.replace(/(_|%|\\)/g, '\\$1');

          if (sanitizedSearch) {
            const ilikeQuery = `%${sanitizedSearch}%`;
            where[Op.or] = [
              { description: { [Op.iLike]: ilikeQuery } },
              { longDescription: { [Op.iLike]: ilikeQuery } },
              { slug: { [Op.iLike]: ilikeQuery } },
              { name: { [Op.iLike]: ilikeQuery } },
              { tags: { [Op.overlap]: sequelize.cast([args.searchTerm.toLowerCase()], 'varchar[]') } },
            ];

            if (/^#?\d+$/.test(args.searchTerm)) {
              where[Op.or].push({ id: args.searchTerm.replace('#', '') });
            }
          }

          const result = await models.Collective.findAndCountAll({
            where,
            limit: args.limit,
            offset: args.offset,
            order: [[args.orderBy.field, args.orderBy.direction]],
          });

          // Link applications to collectives
          const collectiveIds = result.rows.map(collective => collective.id);
          const applications = await models.HostApplication.findAll({
            order: [['updatedAt', 'DESC']],
            where: {
              HostCollectiveId: host.id,
              status: 'PENDING',
              CollectiveId: collectiveIds ? { [Op.in]: collectiveIds } : undefined,
            },
          });
          const groupedApplications = keyBy(applications, 'CollectiveId');
          const nodes = result.rows.map(collective => {
            const application = groupedApplications[collective.id];
            if (application) {
              application.collective = collective;
              return application;
            } else {
              return { collective };
            }
          });

          return { totalCount: result.count, limit: args.limit, offset: args.offset, nodes };
        },
      },
    };
  },
});
<commit_msg>fix(pendingApplications): Only return collectives<commit_after>import { GraphQLBoolean, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
import { find, get, keyBy, mapValues } from 'lodash';

import { types as CollectiveType } from '../../../constants/collectives';
import { PAYMENT_METHOD_SERVICE, PAYMENT_METHOD_TYPE } from '../../../constants/paymentMethods';
import models, { Op, sequelize } from '../../../models';
import { PayoutMethodTypes } from '../../../models/PayoutMethod';
import TransferwiseLib from '../../../paymentProviders/transferwise';
import { Unauthorized } from '../../errors';
import { HostApplicationCollection } from '../collection/HostApplicationCollection';
import { PaymentMethodType, PayoutMethodType } from '../enum';
import { ChronologicalOrderInput } from '../input/ChronologicalOrderInput';
import { Account, AccountFields } from '../interface/Account';
import { AccountWithContributions, AccountWithContributionsFields } from '../interface/AccountWithContributions';
import { CollectionArgs } from '../interface/Collection';
import URL from '../scalar/URL';

import { Amount } from './Amount';
import { HostMetrics } from './HostMetrics';
import { HostPlan } from './HostPlan';
import { PaymentMethod } from './PaymentMethod';
import PayoutMethod from './PayoutMethod';

export const Host = new GraphQLObjectType({
  name: 'Host',
  description: 'This represents an Host account',
  interfaces: () => [Account, AccountWithContributions],
  fields: () => {
    return {
      ...AccountFields,
      ...AccountWithContributionsFields,
      hostFeePercent: {
        type: GraphQLInt,
        resolve(collective) {
          return collective.hostFeePercent;
        },
      },
      totalHostedCollectives: {
        type: GraphQLInt,
        resolve(collective) {
          return collective.getHostedCollectivesCount();
        },
      },
      isOpenToApplications: {
        type: GraphQLBoolean,
        resolve(collective) {
          return collective.canApply();
        },
      },
      termsUrl: {
        type: URL,
        resolve(collective) {
          return get(collective, 'settings.tos');
        },
      },
      plan: {
        type: new GraphQLNonNull(HostPlan),
        resolve(host) {
          return host.getPlan();
        },
      },
      hostMetrics: {
        type: new GraphQLNonNull(HostMetrics),
        args: {
          from: {
            type: GraphQLString,
            description: "Inferior date limit in which we're calculating the metrics",
          },
          to: {
            type: GraphQLString,
            description: "Superior date limit in which we're calculating the metrics",
          },
        },
        async resolve(host, args) {
          const metrics = await host.getHostMetrics(args?.from, args?.to);
          const toAmount = value => ({ value, currency: host.currency });
          return mapValues(metrics, (value, key) => (key.includes('Percent') ? value : toAmount(value)));
        },
      },
      supportedPaymentMethods: {
        type: new GraphQLList(PaymentMethodType),
        description:
          'The list of payment methods (Stripe, Paypal, manual bank transfer, etc ...) the Host can accept for its Collectives',
        async resolve(collective, _, req) {
          const supportedPaymentMethods = [];

          // Paypal, Stripe = connected accounts
          const connectedAccounts = await req.loaders.Collective.connectedAccounts.load(collective.id);

          if (find(connectedAccounts, ['service', 'stripe'])) {
            supportedPaymentMethods.push('CREDIT_CARD');
          }

          if (find(connectedAccounts, ['service', 'paypal']) && !collective.settings?.disablePaypalDonations) {
            supportedPaymentMethods.push('PAYPAL');
          }

          // bank transfer = manual in host settings
          if (get(collective, 'settings.paymentMethods.manual', null)) {
            supportedPaymentMethods.push('BANK_TRANSFER');
          }

          return supportedPaymentMethods;
        },
      },
      bankAccount: {
        type: PayoutMethod,
        async resolve(collective, _, req) {
          const payoutMethods = await req.loaders.PayoutMethod.byCollectiveId.load(collective.id);
          return payoutMethods.find(c => c.type === 'BANK_ACCOUNT' && c.data?.isManualBankTransfer);
        },
      },
      paypalPreApproval: {
        type: PaymentMethod,
        description: 'Paypal preapproval info. Returns null if PayPal account is not connected.',
        resolve: async host => {
          return models.PaymentMethod.findOne({
            where: {
              CollectiveId: host.id,
              service: PAYMENT_METHOD_SERVICE.PAYPAL,
              type: PAYMENT_METHOD_TYPE.ADAPTIVE,
            },
          });
        },
      },
      supportedPayoutMethods: {
        type: new GraphQLList(PayoutMethodType),
        description: 'The list of payout methods this Host accepts for its expenses',
        async resolve(collective, _, req) {
          const connectedAccounts = await req.loaders.Collective.connectedAccounts.load(collective.id);
          const supportedPayoutMethods = [PayoutMethodTypes.OTHER, PayoutMethodTypes.ACCOUNT_BALANCE];
          if (connectedAccounts?.find?.(c => c.service === 'transferwise')) {
            supportedPayoutMethods.push(PayoutMethodTypes.BANK_ACCOUNT);
          }
          if (connectedAccounts?.find?.(c => c.service === 'paypal') || !collective.settings?.disablePaypalPayouts) {
            supportedPayoutMethods.push(PayoutMethodTypes.PAYPAL);
          }

          return supportedPayoutMethods;
        },
      },
      transferwiseBalances: {
        type: new GraphQLList(Amount),
        description: 'Transferwise balances. Returns null if Transferwise account is not connected.',
        resolve: async host => {
          const transferwiseAccount = await models.ConnectedAccount.findOne({
            where: { CollectiveId: host.id, service: 'transferwise' },
          });

          if (transferwiseAccount) {
            return TransferwiseLib.getAccountBalances(transferwiseAccount).then(balances => {
              return balances.map(balance => ({
                value: Math.round(balance.amount.value * 100),
                currency: balance.amount.currency,
              }));
            });
          }
        },
      },
      pendingApplications: {
        type: new GraphQLNonNull(HostApplicationCollection),
        description: 'Pending applications for this host',
        args: {
          ...CollectionArgs,
          searchTerm: {
            type: GraphQLString,
            description:
              'A term to search membership. Searches in collective tags, name, slug, members description and role.',
          },
          orderBy: {
            type: new GraphQLNonNull(ChronologicalOrderInput),
            defaultValue: { field: 'createdAt', direction: 'DESC' },
            description: 'Order of the results',
          },
        },
        resolve: async (host, args, req) => {
          if (!req.remoteUser?.isAdmin(host.id)) {
            throw new Unauthorized('You need to be logged in as an admin of the host to see its pending application');
          }

          const applyTypes = [CollectiveType.COLLECTIVE, CollectiveType.FUND];
          const where = { HostCollectiveId: host.id, approvedAt: null, type: { [Op.in]: applyTypes } };
          const sanitizedSearch = args.searchTerm?.replace(/(_|%|\\)/g, '\\$1');

          if (sanitizedSearch) {
            const ilikeQuery = `%${sanitizedSearch}%`;
            where[Op.or] = [
              { description: { [Op.iLike]: ilikeQuery } },
              { longDescription: { [Op.iLike]: ilikeQuery } },
              { slug: { [Op.iLike]: ilikeQuery } },
              { name: { [Op.iLike]: ilikeQuery } },
              { tags: { [Op.overlap]: sequelize.cast([args.searchTerm.toLowerCase()], 'varchar[]') } },
            ];

            if (/^#?\d+$/.test(args.searchTerm)) {
              where[Op.or].push({ id: args.searchTerm.replace('#', '') });
            }
          }

          const result = await models.Collective.findAndCountAll({
            where,
            limit: args.limit,
            offset: args.offset,
            order: [[args.orderBy.field, args.orderBy.direction]],
          });

          // Link applications to collectives
          const collectiveIds = result.rows.map(collective => collective.id);
          const applications = await models.HostApplication.findAll({
            order: [['updatedAt', 'DESC']],
            where: {
              HostCollectiveId: host.id,
              status: 'PENDING',
              CollectiveId: collectiveIds ? { [Op.in]: collectiveIds } : undefined,
            },
          });
          const groupedApplications = keyBy(applications, 'CollectiveId');
          const nodes = result.rows.map(collective => {
            const application = groupedApplications[collective.id];
            if (application) {
              application.collective = collective;
              return application;
            } else {
              return { collective };
            }
          });

          return { totalCount: result.count, limit: args.limit, offset: args.offset, nodes };
        },
      },
    };
  },
});
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import {isNil, pluck, omit, type} from 'ramda';
import React, {Component} from 'react';
import ReactDropdown from 'react-virtualized-select';
import createFilterOptions from 'react-select-fast-filter-options';
import './css/react-virtualized-select@3.1.0.css';
import './css/react-virtualized@9.9.0.css';

// Custom tokenizer, see https://github.com/bvaughn/js-search/issues/43
// Split on spaces
const REGEX = /\s+/;
const TOKENIZER = {
    tokenize(text) {
        return text.split(REGEX).filter(
            // Filter empty tokens
            text => text
        );
    },
};

const DELIMETER = ',';

/**
 * Dropdown is an interactive dropdown element for selecting one or more
 * items.
 * The values and labels of the dropdown items are specified in the `options`
 * property and the selected item(s) are specified with the `value` property.
 *
 * Use a dropdown when you have many options (more than 5) or when you are
 * constrained for space. Otherwise, you can use RadioItems or a Checklist,
 * which have the benefit of showing the users all of the items at once.
 */
export default class Dropdown extends Component {
    constructor(props) {
        super(props);
        this.state = {
            filterOptions: createFilterOptions({
                options: props.options,
                tokenizer: TOKENIZER,
            }),
        };
    }

    componentWillReceiveProps(newProps) {
        if (newProps.options !== this.props.options) {
            this.setState({
                filterOptions: createFilterOptions({
                    options: newProps.options,
                    tokenizer: TOKENIZER,
                }),
            });
        }
    }

    render() {
        const {
            id,
            multi,
            options,
            setProps,
            style,
            loading_state,
            value,
        } = this.props;
        const {filterOptions} = this.state;
        let selectedValue;
        if (type(value) === 'array') {
            selectedValue = value.join(DELIMETER);
        } else {
            selectedValue = value;
        }
        return (
            <div
                id={id}
                style={style}
                data-dash-is-loading={
                    (loading_state && loading_state.is_loading) || undefined
                }
            >
                <ReactDropdown
                    filterOptions={filterOptions}
                    options={options}
                    value={selectedValue}
                    onChange={selectedOption => {
                        if (multi) {
                            let value;
                            if (isNil(selectedOption)) {
                                value = [];
                            } else {
                                value = pluck('value', selectedOption);
                            }
                            setProps({value});
                        } else {
                            let value;
                            if (isNil(selectedOption)) {
                                value = null;
                            } else {
                                value = selectedOption.value;
                            }
                            setProps({value});
                        }
                    }}
                    {...omit(['setProps', 'value'], this.props)}
                />
            </div>
        );
    }
}

Dropdown.propTypes = {
    /**
     * The ID of this component, used to identify dash components
     * in callbacks. The ID needs to be unique across all of the
     * components in an app.
     */
    id: PropTypes.string,

    /**
     * An array of options {lable: [string|number], value: [string|number]},
     * an optional disabled field can be used for each option
     */
    options: PropTypes.arrayOf(
        PropTypes.exact({
            /**
             * The dropdown's label
             */
            label: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
                .isRequired,

            /**
             * The value of the dropdown. This value
             * corresponds to the items specified in the
             * `values` property.
             */
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
                .isRequired,

            /**
             * If true, this dropdown is disabled and items can't be selected.
             */
            disabled: PropTypes.bool,
        })
    ),

    /**
     * The value of the input. If `multi` is false (the default)
     * then value is just a string that corresponds to the values
     * provided in the `options` property. If `multi` is true, then
     * multiple values can be selected at once, and `value` is an
     * array of items with values corresponding to those in the
     * `options` prop.
     */
    value: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.arrayOf(PropTypes.string),
        PropTypes.number,
        PropTypes.arrayOf(PropTypes.number),
    ]),

    /**
     * className of the dropdown element
     */
    className: PropTypes.string,

    /**
     * Whether or not the dropdown is "clearable", that is, whether or
     * not a small "x" appears on the right of the dropdown that removes
     * the selected value.
     */
    clearable: PropTypes.bool,

    /**
     * If true, the option is disabled
     */
    disabled: PropTypes.bool,

    /**
     * If true, the user can select multiple values
     */
    multi: PropTypes.bool,

    /**
     * The grey, default text shown when no option is selected
     */
    placeholder: PropTypes.string,

    /**
     * Whether to enable the searching feature or not
     */
    searchable: PropTypes.bool,

    /**
     * Dash-assigned callback that gets fired when the input changes
     */
    setProps: PropTypes.func,

    /**
     * Defines CSS styles which will override styles previously set.
     */
    style: PropTypes.object,

    /**
     * Object that holds the loading state object coming from dash-renderer
     */
    loading_state: PropTypes.shape({
        /**
         * Determines if the component is loading or not
         */
        is_loading: PropTypes.bool,
        /**
         * Holds which property is loading
         */
        prop_name: PropTypes.string,
        /**
         * Holds the name of the component that is loading
         */
        component_name: PropTypes.string,
    }),
};

Dropdown.defaultProps = {
    clearable: true,
    disabled: false,
    multi: false,
    searchable: true,
};
<commit_msg>:pencil2: typo<commit_after>import PropTypes from 'prop-types';
import {isNil, pluck, omit, type} from 'ramda';
import React, {Component} from 'react';
import ReactDropdown from 'react-virtualized-select';
import createFilterOptions from 'react-select-fast-filter-options';
import './css/react-virtualized-select@3.1.0.css';
import './css/react-virtualized@9.9.0.css';

// Custom tokenizer, see https://github.com/bvaughn/js-search/issues/43
// Split on spaces
const REGEX = /\s+/;
const TOKENIZER = {
    tokenize(text) {
        return text.split(REGEX).filter(
            // Filter empty tokens
            text => text
        );
    },
};

const DELIMETER = ',';

/**
 * Dropdown is an interactive dropdown element for selecting one or more
 * items.
 * The values and labels of the dropdown items are specified in the `options`
 * property and the selected item(s) are specified with the `value` property.
 *
 * Use a dropdown when you have many options (more than 5) or when you are
 * constrained for space. Otherwise, you can use RadioItems or a Checklist,
 * which have the benefit of showing the users all of the items at once.
 */
export default class Dropdown extends Component {
    constructor(props) {
        super(props);
        this.state = {
            filterOptions: createFilterOptions({
                options: props.options,
                tokenizer: TOKENIZER,
            }),
        };
    }

    componentWillReceiveProps(newProps) {
        if (newProps.options !== this.props.options) {
            this.setState({
                filterOptions: createFilterOptions({
                    options: newProps.options,
                    tokenizer: TOKENIZER,
                }),
            });
        }
    }

    render() {
        const {
            id,
            multi,
            options,
            setProps,
            style,
            loading_state,
            value,
        } = this.props;
        const {filterOptions} = this.state;
        let selectedValue;
        if (type(value) === 'array') {
            selectedValue = value.join(DELIMETER);
        } else {
            selectedValue = value;
        }
        return (
            <div
                id={id}
                style={style}
                data-dash-is-loading={
                    (loading_state && loading_state.is_loading) || undefined
                }
            >
                <ReactDropdown
                    filterOptions={filterOptions}
                    options={options}
                    value={selectedValue}
                    onChange={selectedOption => {
                        if (multi) {
                            let value;
                            if (isNil(selectedOption)) {
                                value = [];
                            } else {
                                value = pluck('value', selectedOption);
                            }
                            setProps({value});
                        } else {
                            let value;
                            if (isNil(selectedOption)) {
                                value = null;
                            } else {
                                value = selectedOption.value;
                            }
                            setProps({value});
                        }
                    }}
                    {...omit(['setProps', 'value'], this.props)}
                />
            </div>
        );
    }
}

Dropdown.propTypes = {
    /**
     * The ID of this component, used to identify dash components
     * in callbacks. The ID needs to be unique across all of the
     * components in an app.
     */
    id: PropTypes.string,

    /**
     * An array of options {label: [string|number], value: [string|number]},
     * an optional disabled field can be used for each option
     */
    options: PropTypes.arrayOf(
        PropTypes.exact({
            /**
             * The dropdown's label
             */
            label: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
                .isRequired,

            /**
             * The value of the dropdown. This value
             * corresponds to the items specified in the
             * `values` property.
             */
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
                .isRequired,

            /**
             * If true, this dropdown is disabled and items can't be selected.
             */
            disabled: PropTypes.bool,
        })
    ),

    /**
     * The value of the input. If `multi` is false (the default)
     * then value is just a string that corresponds to the values
     * provided in the `options` property. If `multi` is true, then
     * multiple values can be selected at once, and `value` is an
     * array of items with values corresponding to those in the
     * `options` prop.
     */
    value: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.arrayOf(PropTypes.string),
        PropTypes.number,
        PropTypes.arrayOf(PropTypes.number),
    ]),

    /**
     * className of the dropdown element
     */
    className: PropTypes.string,

    /**
     * Whether or not the dropdown is "clearable", that is, whether or
     * not a small "x" appears on the right of the dropdown that removes
     * the selected value.
     */
    clearable: PropTypes.bool,

    /**
     * If true, the option is disabled
     */
    disabled: PropTypes.bool,

    /**
     * If true, the user can select multiple values
     */
    multi: PropTypes.bool,

    /**
     * The grey, default text shown when no option is selected
     */
    placeholder: PropTypes.string,

    /**
     * Whether to enable the searching feature or not
     */
    searchable: PropTypes.bool,

    /**
     * Dash-assigned callback that gets fired when the input changes
     */
    setProps: PropTypes.func,

    /**
     * Defines CSS styles which will override styles previously set.
     */
    style: PropTypes.object,

    /**
     * Object that holds the loading state object coming from dash-renderer
     */
    loading_state: PropTypes.shape({
        /**
         * Determines if the component is loading or not
         */
        is_loading: PropTypes.bool,
        /**
         * Holds which property is loading
         */
        prop_name: PropTypes.string,
        /**
         * Holds the name of the component that is loading
         */
        component_name: PropTypes.string,
    }),
};

Dropdown.defaultProps = {
    clearable: true,
    disabled: false,
    multi: false,
    searchable: true,
};
<|endoftext|>