<commit_before>'use strict';

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

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

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

module.exports = class coinmate extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'coinmate',
            'name': 'CoinMate',
            'countries': [ 'GB', 'CZ', 'EU' ], // UK, Czech Republic
            'rateLimit': 1000,
            'has': {
                'CORS': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27811229-c1efb510-606c-11e7-9a36-84ba2ce412d8.jpg',
                'api': 'https://coinmate.io/api',
                'www': 'https://coinmate.io',
                'fees': 'https://coinmate.io/fees',
                'doc': [
                    'https://coinmate.docs.apiary.io',
                    'https://coinmate.io/developers',
                ],
                'referral': 'https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0',
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
                'uid': true,
            },
            'api': {
                'public': {
                    'get': [
                        'orderBook',
                        'ticker',
                        'transactions',
                        'tradingPairs',
                    ],
                },
                'private': {
                    'post': [
                        'balances',
                        'bitcoinCashWithdrawal',
                        'bitcoinCashDepositAddresses',
                        'bitcoinDepositAddresses',
                        'bitcoinWithdrawal',
                        'bitcoinWithdrawalFees',
                        'buyInstant',
                        'buyLimit',
                        'cancelOrder',
                        'cancelOrderWithInfo',
                        'createVoucher',
                        'dashDepositAddresses',
                        'dashWithdrawal',
                        'ethereumWithdrawal',
                        'ethereumDepositAddresses',
                        'litecoinWithdrawal',
                        'litecoinDepositAddresses',
                        'openOrders',
                        'order',
                        'orderHistory',
                        'pusherAuth',
                        'redeemVoucher',
                        'replaceByBuyLimit',
                        'replaceByBuyInstant',
                        'replaceBySellLimit',
                        'replaceBySellInstant',
                        'rippleDepositAddresses',
                        'rippleWithdrawal',
                        'sellInstant',
                        'sellLimit',
                        'transactionHistory',
                        'traderFees',
                        'tradeHistory',
                        'transfer',
                        'transferHistory',
                        'unconfirmedBitcoinDeposits',
                        'unconfirmedBitcoinCashDeposits',
                        'unconfirmedDashDeposits',
                        'unconfirmedEthereumDeposits',
                        'unconfirmedLitecoinDeposits',
                        'unconfirmedRippleDeposits',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.05 / 100,
                    'taker': 0.15 / 100,
                },
            },
        });
    }

    async fetchMarkets (params = {}) {
        const response = await this.publicGetTradingPairs (params);
        //
        //     {
        //         "error":false,
        //         "errorMessage":null,
        //         "data": [
        //             {
        //                 "name":"BTC_EUR",
        //                 "firstCurrency":"BTC",
        //                 "secondCurrency":"EUR",
        //                 "priceDecimals":2,
        //                 "lotDecimals":8,
        //                 "minAmount":0.0002,
        //                 "tradesWebSocketChannelId":"trades-BTC_EUR",
        //                 "orderBookWebSocketChannelId":"order_book-BTC_EUR",
        //                 "tradeStatisticsWebSocketChannelId":"statistics-BTC_EUR"
        //             },
        //         ]
        //     }
        //
        const data = this.safeValue (response, 'data');
        const result = [];
        for (let i = 0; i < data.length; i++) {
            const market = data[i];
            const id = this.safeString (market, 'name');
            const baseId = this.safeString (market, 'firstCurrency');
            const quoteId = this.safeString (market, 'secondCurrency');
            const base = this.commonCurrencyCode (baseId);
            const quote = this.commonCurrencyCode (quoteId);
            const symbol = base + '/' + quote;
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': undefined,
                'info': market,
                'precision': {
                    'price': this.safeInteger (market, 'priceDecimals'),
                    'amount': this.safeInteger (market, 'lotDecimals'),
                },
                'limits': {
                    'amount': {
                        'min': this.safeFloat (market, 'minAmount'),
                        'max': undefined,
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        const response = await this.privatePostBalances (params);
        const balances = this.safeValue (response, 'data');
        const result = { 'info': response };
        const currencyIds = Object.keys (balances);
        for (let i = 0; i < currencyIds.length; i++) {
            const currencyId = currencyIds[i];
            const code = this.commonCurrencyCode (currencyId);
            const balance = this.safeValue (balances, currencyId);
            const account = this.account ();
            account['free'] = this.safeFloat (balance, 'available');
            account['used'] = this.safeFloat (balance, 'reserved');
            account['total'] = this.safeFloat (balance, 'balance');
            result[code] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {
            'currencyPair': this.marketId (symbol),
            'groupByPriceLimit': 'False',
        };
        const response = await this.publicGetOrderBook (this.extend (request, params));
        const orderbook = response['data'];
        let timestamp = this.safeInteger (orderbook, 'timestamp');
        if (timestamp !== undefined) {
            timestamp *= 1000;
        }
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        const request = {
            'currencyPair': this.marketId (symbol),
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        const ticker = this.safeValue (response, 'data');
        let timestamp = this.safeInteger (ticker, 'timestamp');
        if (timestamp !== undefined) {
            timestamp = timestamp * 1000;
        }
        const last = this.safeFloat (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'ask'),
            'vwap': undefined,
            'askVolume': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'amount'),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        if (limit === undefined) {
            limit = 1000;
        }
        const request = {
            'limit': limit,
        };
        if (since !== undefined) {
            request['timestampFrom'] = since;
        }
        const response = await this.privatePostTradeHistory (this.extend (request, params));
        const items = response['data'];
        return this.parseTrades (items, undefined, since, limit);
    }

    parseTrade (trade, market = undefined) {
        if ('createdTimestamp' in trade) {
            return this.parsePrivateTrade (trade, market);
        } else {
            return this.parsePublicTrade (trade, market);
        }
    }

    parsePrivateTrade (item, market = undefined) {
        // { transactionId: 2671819,
        //     createdTimestamp: 1529649127605,
        //     currencyPair: 'LTC_BTC',
        //     type: 'BUY',
        //     orderType: 'LIMIT',
        //     orderId: 101810227,
        //     amount: 0.01,
        //     price: 0.01406,
        //     fee: 0,
        //     feeType: 'MAKER' }
        const timestamp = this.safeInteger (item, 'createdTimestamp');
        const amount = this.safeFloat (item, 'amount');
        const price = this.safeFloat (item, 'price');
        const fee = this.safeFloat (item, 'fee');
        const currencyPair = this.safeString (item, 'currencyPair');
        market = this.findMarket (currencyPair);
        const side = this.safeString (item, 'type').toLowerCase ();
        const type = this.safeString (item, 'orderType').toLowerCase ();
        const takerOrMaker = this.safeString (item, 'feeType') === 'MAKER' ? 'maker' : 'taker';
        const cost = amount * price;
        return {
            'id': this.safeString (item, 'transactionId'),
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'type': type,
            'order': this.safeString (item, 'orderId'),
            'takerOrMaker': takerOrMaker,
            'fee': {
                'cost': fee,
                'currency': market['quote'],
            },
            'info': item,
        };
    }

    parsePublicTrade (trade, market = undefined) {
        let symbol = undefined;
        if (market === undefined) {
            const marketId = this.safeString (trade, 'currencyPair');
            if (marketId in this.markets_by_id[marketId]) {
                market = this.markets_by_id[marketId];
            }
        }
        if (market !== undefined) {
            symbol = market['symbol'];
        }
        const price = this.safeFloat (trade, 'price');
        const amount = this.safeFloat (trade, 'amount');
        let cost = undefined;
        if (amount !== undefined) {
            if (price !== undefined) {
                cost = price * amount;
            }
        }
        const id = this.safeString (trade, 'transactionId');
        const timestamp = this.safeInteger (trade, 'timestamp');
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'type': undefined,
            'side': undefined,
            'order': undefined,
            'takerOrMaker': undefined,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currencyPair': market['id'],
            'minutesIntoHistory': 10,
        };
        const response = await this.publicGetTransactions (this.extend (request, params));
        return this.parseTrades (response['data'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let method = 'privatePost' + this.capitalize (side);
        const request = {
            'currencyPair': this.marketId (symbol),
        };
        if (type === 'market') {
            if (side === 'buy') {
                request['total'] = amount; // amount in fiat
            } else {
                request['amount'] = amount; // amount in fiat
            }
            method += 'Instant';
        } else {
            request['amount'] = amount; // amount in crypto
            request['price'] = price;
            method += this.capitalize (type);
        }
        const response = await this[method] (this.extend (request, params));
        return {
            'info': response,
            'id': response['data'].toString (),
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        return await this.privatePostCancelOrder ({ 'orderId': id });
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length) {
                url += '?' + this.urlencode (params);
            }
        } else {
            this.checkRequiredCredentials ();
            const nonce = this.nonce ().toString ();
            const auth = nonce + this.uid + this.apiKey;
            const signature = this.hmac (this.encode (auth), this.encode (this.secret));
            body = this.urlencode (this.extend ({
                'clientId': this.uid,
                'nonce': nonce,
                'publicKey': this.apiKey,
                'signature': signature.toUpperCase (),
            }, params));
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        const response = await this.fetch2 (path, api, method, params, headers, body);
        if ('error' in response) {
            if (response['error']) {
                throw new ExchangeError (this.id + ' ' + this.json (response));
            }
        }
        return response;
    }
};
<commit_msg>[coinmate] added fetchTransactions<commit_after>'use strict';

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

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

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

module.exports = class coinmate extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'coinmate',
            'name': 'CoinMate',
            'countries': [ 'GB', 'CZ', 'EU' ], // UK, Czech Republic
            'rateLimit': 1000,
            'has': {
                'CORS': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27811229-c1efb510-606c-11e7-9a36-84ba2ce412d8.jpg',
                'api': 'https://coinmate.io/api',
                'www': 'https://coinmate.io',
                'fees': 'https://coinmate.io/fees',
                'doc': [
                    'https://coinmate.docs.apiary.io',
                    'https://coinmate.io/developers',
                ],
                'referral': 'https://coinmate.io?referral=YTFkM1RsOWFObVpmY1ZjMGREQmpTRnBsWjJJNVp3PT0',
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
                'uid': true,
            },
            'api': {
                'public': {
                    'get': [
                        'orderBook',
                        'ticker',
                        'transactions',
                        'tradingPairs',
                    ],
                },
                'private': {
                    'post': [
                        'balances',
                        'bitcoinCashWithdrawal',
                        'bitcoinCashDepositAddresses',
                        'bitcoinDepositAddresses',
                        'bitcoinWithdrawal',
                        'bitcoinWithdrawalFees',
                        'buyInstant',
                        'buyLimit',
                        'cancelOrder',
                        'cancelOrderWithInfo',
                        'createVoucher',
                        'dashDepositAddresses',
                        'dashWithdrawal',
                        'ethereumWithdrawal',
                        'ethereumDepositAddresses',
                        'litecoinWithdrawal',
                        'litecoinDepositAddresses',
                        'openOrders',
                        'order',
                        'orderHistory',
                        'pusherAuth',
                        'redeemVoucher',
                        'replaceByBuyLimit',
                        'replaceByBuyInstant',
                        'replaceBySellLimit',
                        'replaceBySellInstant',
                        'rippleDepositAddresses',
                        'rippleWithdrawal',
                        'sellInstant',
                        'sellLimit',
                        'transactionHistory',
                        'traderFees',
                        'tradeHistory',
                        'transfer',
                        'transferHistory',
                        'unconfirmedBitcoinDeposits',
                        'unconfirmedBitcoinCashDeposits',
                        'unconfirmedDashDeposits',
                        'unconfirmedEthereumDeposits',
                        'unconfirmedLitecoinDeposits',
                        'unconfirmedRippleDeposits',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.05 / 100,
                    'taker': 0.15 / 100,
                },
            },
        });
    }

    async fetchMarkets (params = {}) {
        const response = await this.publicGetTradingPairs (params);
        //
        //     {
        //         "error":false,
        //         "errorMessage":null,
        //         "data": [
        //             {
        //                 "name":"BTC_EUR",
        //                 "firstCurrency":"BTC",
        //                 "secondCurrency":"EUR",
        //                 "priceDecimals":2,
        //                 "lotDecimals":8,
        //                 "minAmount":0.0002,
        //                 "tradesWebSocketChannelId":"trades-BTC_EUR",
        //                 "orderBookWebSocketChannelId":"order_book-BTC_EUR",
        //                 "tradeStatisticsWebSocketChannelId":"statistics-BTC_EUR"
        //             },
        //         ]
        //     }
        //
        const data = this.safeValue (response, 'data');
        const result = [];
        for (let i = 0; i < data.length; i++) {
            const market = data[i];
            const id = this.safeString (market, 'name');
            const baseId = this.safeString (market, 'firstCurrency');
            const quoteId = this.safeString (market, 'secondCurrency');
            const base = this.commonCurrencyCode (baseId);
            const quote = this.commonCurrencyCode (quoteId);
            const symbol = base + '/' + quote;
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': undefined,
                'info': market,
                'precision': {
                    'price': this.safeInteger (market, 'priceDecimals'),
                    'amount': this.safeInteger (market, 'lotDecimals'),
                },
                'limits': {
                    'amount': {
                        'min': this.safeFloat (market, 'minAmount'),
                        'max': undefined,
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        const response = await this.privatePostBalances (params);
        const balances = this.safeValue (response, 'data');
        const result = { 'info': response };
        const currencyIds = Object.keys (balances);
        for (let i = 0; i < currencyIds.length; i++) {
            const currencyId = currencyIds[i];
            const code = this.commonCurrencyCode (currencyId);
            const balance = this.safeValue (balances, currencyId);
            const account = this.account ();
            account['free'] = this.safeFloat (balance, 'available');
            account['used'] = this.safeFloat (balance, 'reserved');
            account['total'] = this.safeFloat (balance, 'balance');
            result[code] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {
            'currencyPair': this.marketId (symbol),
            'groupByPriceLimit': 'False',
        };
        const response = await this.publicGetOrderBook (this.extend (request, params));
        const orderbook = response['data'];
        let timestamp = this.safeInteger (orderbook, 'timestamp');
        if (timestamp !== undefined) {
            timestamp *= 1000;
        }
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        const request = {
            'currencyPair': this.marketId (symbol),
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        const ticker = this.safeValue (response, 'data');
        let timestamp = this.safeInteger (ticker, 'timestamp');
        if (timestamp !== undefined) {
            timestamp = timestamp * 1000;
        }
        const last = this.safeFloat (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'ask'),
            'vwap': undefined,
            'askVolume': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'amount'),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {
            'limit': 1000,
        };
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        if (since !== undefined) {
            request['timestampFrom'] = since;
        }
        if (code !== undefined) {
            request['currency'] = this.currencyId (code);
        }
        const response = await this.privatePostTransferHistory (this.extend (request, params));
        const items = response['data'];
        return this.parseTransactions (items, undefined, since, limit);
    }

    parseTransaction (item, currency = undefined) {
        //   { transactionId: 1862815,
        //     timestamp: 1516803982388,
        //     amountCurrency: 'LTC',
        //     amount: 1,
        //     fee: 0,
        //     walletType: 'LTC',
        //     transferType: 'DEPOSIT',
        //     transferStatus: 'COMPLETED',
        //     txid:
        //     'ccb9255dfa874e6c28f1a64179769164025329d65e5201849c2400abd6bce245',
        //         destination: 'LQrtSKA6LnhcwRrEuiborQJnjFF56xqsFn',
        //     destinationTag: null }
        //
        //   { transactionId: 2140966,
        //     timestamp: 1519314282976,
        //     amountCurrency: 'EUR',
        //     amount: 8421.7228,
        //     fee: 16.8772,
        //     walletType: 'BANK_WIRE',
        //     transferType: 'WITHDRAWAL',
        //     transferStatus: 'COMPLETED',
        //     txid: null,
        //     destination: null,
        //     destinationTag: null }
        const timestamp = this.safeInteger (item, 'timestamp');
        const amount = this.safeFloat (item, 'amount');
        const fee = this.safeFloat (item, 'fee');
        const txid = this.safeString (item, 'txid', undefined);
        const address = this.safeString (item, 'destination', undefined);
        const tag = this.safeString (item, 'destinationTag', undefined);
        const amountCurrency = this.safeString (item, 'amountCurrency');
        currency = this.commonCurrencyCode (amountCurrency);
        const type = this.safeString (item, 'transferType').toLowerCase ();
        const transferStatus = this.safeString (item, 'transferStatus');
        let status = undefined;
        if (transferStatus === 'COMPLETED') {
            status = 'ok';
        }
        return {
            'id': this.safeString (item, 'transactionId'),
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'currency': currency,
            'amount': amount,
            'type': type,
            'txid': txid,
            'address': address,
            'tag': tag,
            'status': status,
            'fee': {
                'cost': fee,
                'currency': currency,
            },
            'info': item,
        };
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        if (limit === undefined) {
            limit = 1000;
        }
        const request = {
            'limit': limit,
        };
        if (since !== undefined) {
            request['timestampFrom'] = since;
        }
        const response = await this.privatePostTradeHistory (this.extend (request, params));
        const items = response['data'];
        return this.parseTrades (items, undefined, since, limit);
    }

    parseTrade (trade, market = undefined) {
        if ('createdTimestamp' in trade) {
            return this.parsePrivateTrade (trade, market);
        } else {
            return this.parsePublicTrade (trade, market);
        }
    }

    parsePrivateTrade (item, market = undefined) {
        // { transactionId: 2671819,
        //     createdTimestamp: 1529649127605,
        //     currencyPair: 'LTC_BTC',
        //     type: 'BUY',
        //     orderType: 'LIMIT',
        //     orderId: 101810227,
        //     amount: 0.01,
        //     price: 0.01406,
        //     fee: 0,
        //     feeType: 'MAKER' }
        const timestamp = this.safeInteger (item, 'createdTimestamp');
        const amount = this.safeFloat (item, 'amount');
        const price = this.safeFloat (item, 'price');
        const fee = this.safeFloat (item, 'fee');
        const currencyPair = this.safeString (item, 'currencyPair');
        market = this.findMarket (currencyPair);
        const side = this.safeString (item, 'type').toLowerCase ();
        const type = this.safeString (item, 'orderType').toLowerCase ();
        const takerOrMaker = this.safeString (item, 'feeType') === 'MAKER' ? 'maker' : 'taker';
        const cost = amount * price;
        return {
            'id': this.safeString (item, 'transactionId'),
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'type': type,
            'order': this.safeString (item, 'orderId'),
            'takerOrMaker': takerOrMaker,
            'fee': {
                'cost': fee,
                'currency': market['quote'],
            },
            'info': item,
        };
    }

    parsePublicTrade (trade, market = undefined) {
        let symbol = undefined;
        if (market === undefined) {
            const marketId = this.safeString (trade, 'currencyPair');
            if (marketId in this.markets_by_id[marketId]) {
                market = this.markets_by_id[marketId];
            }
        }
        if (market !== undefined) {
            symbol = market['symbol'];
        }
        const price = this.safeFloat (trade, 'price');
        const amount = this.safeFloat (trade, 'amount');
        let cost = undefined;
        if (amount !== undefined) {
            if (price !== undefined) {
                cost = price * amount;
            }
        }
        const id = this.safeString (trade, 'transactionId');
        const timestamp = this.safeInteger (trade, 'timestamp');
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'type': undefined,
            'side': undefined,
            'order': undefined,
            'takerOrMaker': undefined,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currencyPair': market['id'],
            'minutesIntoHistory': 10,
        };
        const response = await this.publicGetTransactions (this.extend (request, params));
        return this.parseTrades (response['data'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let method = 'privatePost' + this.capitalize (side);
        const request = {
            'currencyPair': this.marketId (symbol),
        };
        if (type === 'market') {
            if (side === 'buy') {
                request['total'] = amount; // amount in fiat
            } else {
                request['amount'] = amount; // amount in fiat
            }
            method += 'Instant';
        } else {
            request['amount'] = amount; // amount in crypto
            request['price'] = price;
            method += this.capitalize (type);
        }
        const response = await this[method] (this.extend (request, params));
        return {
            'info': response,
            'id': response['data'].toString (),
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        return await this.privatePostCancelOrder ({ 'orderId': id });
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length) {
                url += '?' + this.urlencode (params);
            }
        } else {
            this.checkRequiredCredentials ();
            const nonce = this.nonce ().toString ();
            const auth = nonce + this.uid + this.apiKey;
            const signature = this.hmac (this.encode (auth), this.encode (this.secret));
            body = this.urlencode (this.extend ({
                'clientId': this.uid,
                'nonce': nonce,
                'publicKey': this.apiKey,
                'signature': signature.toUpperCase (),
            }, params));
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        const response = await this.fetch2 (path, api, method, params, headers, body);
        if ('error' in response) {
            if (response['error']) {
                throw new ExchangeError (this.id + ' ' + this.json (response));
            }
        }
        return response;
    }
};
<|endoftext|>


<commit_before>/*jslint nomen:true, browser: true */
/*global define: false */

define(['haml!haml/columnUi', 'haml!haml/columnUiSelect', 'haml!haml/tupleDisplay', 'colorBar', 'columnMenu', 'config', 'crosshairs', 'defaultTextInput', 'defer', 'kmPlot', 'tooltip', 'util', 'lib/d3', 'jquery', 'lib/select2', 'lib/underscore', 'xenaQuery', 'rx'
	// non-object dependenciies
	], function (template, selectTemplate, tupleTemplate, colorBar, columnMenu, config, crosshairs, defaultTextInput, defer, kmPlot, tooltip, util, d3, $, select2, _, xenaQuery, Rx) {
	'use strict';

	function columnExists(uuid, state) {
		return !!_.get_in(state, ['column_rendering', uuid]);
	}

	var APPLY = true,
		STATIC_URL = config.STATIC_URL,
		moveImg = STATIC_URL + 'heatmap-cavm/images/moveHorizontal.png',
		menuImg = STATIC_URL + 'heatmap-cavm/images/menu.png',
		each = _.each,
		filter = _.filter,
		find = _.find,
		map = _.map,
		reduce = _.reduce,
		toNumber = _.toNumber,
		uniqueId = _.uniqueId,
		widgets = {},
		aWidget;
	aWidget = {
		// this is invoked from columnMenu.js: remove menu function
		destroy: function () {
			this.subs.dispose();
			this.$el.remove();
			this.crosshairs.destroy();
			// TODO clean up subscriptions, subWidgets, like exonRefGene, mutationVector
			kmPlot.destroy(this.id);
			delete widgets[this.id];
			$('.spreadsheet').resize();
		},

		setPlotted: function () {
			var self = this,
				cursor = this.xenaCursor.refine({ '_column': ['_column', self.id] });
			cursor.update(function (s) {
				return _.assoc_in(s, ['_column', 'plotted'], true);
			});
		},

		mouseenterLeave: function (e) {
			var $hoverShow = this.$el.find('.hoverShow'),
				$hoverChange = this.$el.find('.hoverChange');
			if (e.type === 'mouseenter') {
				$hoverShow.removeClass('recede');
				$hoverChange.removeClass('recede');
			} else {
				$hoverShow.addClass('recede');
				$hoverChange.addClass('recede');
				$hoverShow.blur();
			}
		},

		drawLegend: function (colors, labels, align, ellipsis, klass, upperBorderIndex) {
			var label = '';
			if ($('.columnUi').index(this.$el) === 0) {
				label = 'Legend';
			}
			this.$colorBarLabel
				.val(label)
				.addClass(klass);
			labels.reverse();
			this.$colorBarEllipsis.text(ellipsis);
			colorBar.create(this.id, {
				$prevRow: this.$colorBarLabelRow,
				colors: colors.reverse().concat('#808080'),
				labels: labels.concat('NA'),
				tooltips: labels.concat('No data'),
				align: align,
				klass: klass
			});
			$(this.$el.find('.colorTd')[upperBorderIndex]).find('input')
				.css('border-top', '1px #bbbbbb solid');
		},

		setWidth: function (width) {
			this.$moveHandle.width(width - this.$more.width() - 10);
		},

		reRender: function (options) {
			var titlePath = {
					'user': ['column_rendering', this.id, 'columnLabel', 'user'],
					'default': ['column_rendering', this.id, 'columnLabel', 'default']
				},
				fieldPath = {
					'user': ['column_rendering', this.id, 'fieldLabel', 'user'],
					'default': ['column_rendering', this.id, 'fieldLabel', 'default']
				};
			this.ws = options.ws;
			defaultTextInput.create({
				$el: this.$columnTitle,
				state: this.state.refine(titlePath),
				cursor: options.cursor.refine(titlePath),
				id: 'title'
			});
			defaultTextInput.create({
				$el: this.$field,
				state: this.state.refine(fieldPath),
				cursor: options.cursor.refine(fieldPath),
				id: 'field'
			});
		},

		firstRender: function (options) {
			var self = this,
				$anchor = $(options.ws.el);
			this.sheetWrap = options.sheetWrap;
			this.ws = options.ws;
			this.$el = $(template({
				features: undefined,
				moveImg: moveImg,
				menuImg: menuImg
			}));
			$anchor.append(this.$el);

			// adjust to default column dimensions
			this.$el.parent().css('margin-left', this.horizontalMargin);
			this.$el.parent().css('margin-right', this.horizontalMargin);
			this.$el.find('.sparsePad').height(this.sparsePad);
			this.$el.find('.headerPlot').height(this.headerPlotHeight);

			// cache jquery objects for active DOM elements
			this.cache = ['moveHandle', 'more', 'titleRow', 'columnTitle', 'fieldRow', 'field', 'headerPlot', 'sparsePad', 'samplePlot', 'colorBarLabelRow', 'colorBarLabel', 'colorBarEllipsis'];
			_(self).extend(_(self.cache).reduce(function (a, e) { a['$' + e] = self.$el.find('.' + e); return a; }, {}));

			this.columnMenu = columnMenu.create(this.id, {
				anchor: this.$more,
				columnUi: this,
				cursor: this.cursor,
				state: this.state,
				deleteColumn: options.deleteColumn,
				sheetWrap: this.sheetWrap
			});
			this.$el // TODO use rx handlers?
				.on('mouseenter mouseleave', this.mouseenterLeave);

			setTimeout(function () {
				self.setWidth(options.ws.column.width);
			}, 500);
			this.subs.add(this.state.refine({ 'width': ['column_rendering', this.id, 'width'] })
				.subscribe(function (s) {
					self.setWidth(s.width);
				}));

			this.reRender(options);
		},

		render: function (options) {
			if (this.$el) {
				this.reRender(options);
			} else {
				this.firstRender(options);
			}
		},

		kmPlotShow: function () {
			kmPlot.show(this.id, {
				cursor: this.cursor.refine({ 'kmPlot': ['column_rendering', this.id, 'kmPlot'] }),
				columnUi: this
			});
		},

		kmPlotVisibility: function () {
			var myKmState,
				myPlotted,
				self = this;
			this.subs.add(this.state.distinctUntilChanged(function (s) {
				return s.column_rendering[self.id].kmPlot;
			})
				.subscribe(function (s) {
					var kmState = s.column_rendering[self.id].kmPlot,
						plotted = s._column[self.id].plotted;
					if (myKmState && !kmState) {
						myKmState = kmState;
						kmPlot.destroy(self.id);
					}
					if (!myKmState && kmState) {
						myKmState = kmState;
						if (plotted) {
							self.kmPlotShow();
						}
					}
				}));
			this.subs.add(this.state.distinctUntilChanged(function (s) {
				return s._column[self.id].plotted;
			})
				.subscribe(function (s) {
					var kmState = s.column_rendering[self.id].kmPlot,
						plotted = s._column[self.id].plotted;
					if (myPlotted && !plotted) {
						myPlotted = plotted;
					}
					if (!myPlotted && plotted) {
						myPlotted = plotted;
						if (kmState) {
							self.kmPlotShow();
						}
					}
				}));
		},

		initialize: function (options) {
			var self = this;
			_.bindAll.apply(_, [this].concat(_.functions(this)));

			this.subs = new Rx.CompositeDisposable();
			this.sheetWrap = options.sheetWrap;
			this.cursor = options.cursor;
			this.xenaCursor = options.xenaCursor;
			this.state = options.state
				.takeWhile(_.partial(columnExists, this.id))
				.finally(this.destroy)
				.share();
			this.sparsePad = options.sparsePad;
			this.headerPlotHeight = options.headerPlotHeight;
			this.horizontalMargin = options.horizontalMargin.toString() + 'px';

			// initialize the root of this _column in the state tree
			this.xenaCursor.update(function (state) {
				return _.assoc(state, '_column', _.assoc(state._column, self.id, {}));
			});

			if (options.ws) {
				this.render(options);
			}
			this.crosshairs = crosshairs.create(this.id, { $anchor: this.$samplePlot });

			this.subs.add(this.$samplePlot.onAsObservable('click')
				.filter(function (ev) {
					return ev.altKey === true;
				})
				.subscribe(tooltip.toggleFreeze));

			this.kmPlotVisibility();
		}
	};

	function create(id, options) {
		var w = Object.create(aWidget);
		w.id = id;
		w.initialize(options);
		return w;
	}

	return {
		show: function (id, options) {
			var widget = widgets[id];
			if (widget) {
				widget.render(options);
			} else {
				widget = widgets[id] = create(id, options);
			}
			return widget;
		}
	};
});
<commit_msg>ported columnLabel & fieldLabel to columnEdit rx<commit_after>/*jslint nomen:true, browser: true */
/*global define: false */

define(['haml!haml/columnUi', 'haml!haml/columnUiSelect', 'haml!haml/tupleDisplay', 'colorBar', 'columnMenu', 'config', 'crosshairs', 'defaultTextInput', 'defer', 'kmPlot', 'tooltip', 'util', 'lib/d3', 'jquery', 'lib/select2', 'lib/underscore', 'xenaQuery', 'rx'
	// non-object dependenciies
	], function (template, selectTemplate, tupleTemplate, colorBar, columnMenu, config, crosshairs, defaultTextInput, defer, kmPlot, tooltip, util, d3, $, select2, _, xenaQuery, Rx) {
	'use strict';

	function columnExists(uuid, state) {
		return !!_.get_in(state, ['column_rendering', uuid]);
	}

	var APPLY = true,
		STATIC_URL = config.STATIC_URL,
		moveImg = STATIC_URL + 'heatmap-cavm/images/moveHorizontal.png',
		menuImg = STATIC_URL + 'heatmap-cavm/images/menu.png',
		each = _.each,
		filter = _.filter,
		find = _.find,
		map = _.map,
		reduce = _.reduce,
		toNumber = _.toNumber,
		uniqueId = _.uniqueId,
		widgets = {},
		aWidget;
	aWidget = {
		// this is invoked from columnMenu.js: remove menu function
		destroy: function () {
			this.subs.dispose();
			this.$el.remove();
			this.crosshairs.destroy();
			// TODO clean up subscriptions, subWidgets, like exonRefGene, mutationVector
			kmPlot.destroy(this.id);
			delete widgets[this.id];
			$('.spreadsheet').resize();
		},

		setPlotted: function () {
			var self = this,
				cursor = this.xenaCursor.refine({ '_column': ['_column', self.id] });
			cursor.update(function (s) {
				return _.assoc_in(s, ['_column', 'plotted'], true);
			});
		},

		mouseenterLeave: function (e) {
			var $hoverShow = this.$el.find('.hoverShow'),
				$hoverChange = this.$el.find('.hoverChange');
			if (e.type === 'mouseenter') {
				$hoverShow.removeClass('recede');
				$hoverChange.removeClass('recede');
			} else {
				$hoverShow.addClass('recede');
				$hoverChange.addClass('recede');
				$hoverShow.blur();
			}
		},

		drawLegend: function (colors, labels, align, ellipsis, klass, upperBorderIndex) {
			var label = '';
			if ($('.columnUi').index(this.$el) === 0) {
				label = 'Legend';
			}
			this.$colorBarLabel
				.val(label)
				.addClass(klass);
			labels.reverse();
			this.$colorBarEllipsis.text(ellipsis);
			colorBar.create(this.id, {
				$prevRow: this.$colorBarLabelRow,
				colors: colors.reverse().concat('#808080'),
				labels: labels.concat('NA'),
				tooltips: labels.concat('No data'),
				align: align,
				klass: klass
			});
			$(this.$el.find('.colorTd')[upperBorderIndex]).find('input')
				.css('border-top', '1px #bbbbbb solid');
		},

		setWidth: function (width) {
			this.$moveHandle.width(width - this.$more.width() - 10);
		},

		reRender: function (options) {
			var titlePath = {
					'user': ['column_rendering', this.id, 'columnLabel', 'user'],
					'default': ['column_rendering', this.id, 'columnLabel', 'default']
				},
				fieldPath = {
					'user': ['column_rendering', this.id, 'fieldLabel', 'user'],
					'default': ['column_rendering', this.id, 'fieldLabel', 'default']
				};
			this.ws = options.ws;
			defaultTextInput.create({
				$el: this.$columnTitle,
				state: this.state.refine(titlePath),
				cursor: options.cursor.refine(titlePath),
				id: 'title'
			});
			defaultTextInput.create({
				$el: this.$field,
				state: this.state.refine(fieldPath),
				cursor: options.cursor.refine(fieldPath),
				id: 'field'
			});
		},

		firstRender: function (options) {
			var self = this,
				$anchor = $(options.ws.el);
			this.sheetWrap = options.sheetWrap;
			this.ws = options.ws;
			this.$el = $(template({
				features: undefined,
				moveImg: moveImg,
				menuImg: menuImg
			}));
			$anchor.append(this.$el);

			// adjust to default column dimensions
			this.$el.parent().css('margin-left', this.horizontalMargin);
			this.$el.parent().css('margin-right', this.horizontalMargin);
			this.$el.find('.sparsePad').height(this.sparsePad);
			this.$el.find('.headerPlot').height(this.headerPlotHeight);

			// cache jquery objects for active DOM elements
			this.cache = ['moveHandle', 'more', 'titleRow', 'columnTitle', 'fieldRow', 'field', 'headerPlot', 'sparsePad', 'samplePlot', 'colorBarLabelRow', 'colorBarLabel', 'colorBarEllipsis'];
			_(self).extend(_(self.cache).reduce(function (a, e) { a['$' + e] = self.$el.find('.' + e); return a; }, {}));

			this.columnMenu = columnMenu.create(this.id, {
				anchor: this.$more,
				columnUi: this,
				cursor: this.cursor,
				state: this.state,
				deleteColumn: options.deleteColumn,
				sheetWrap: this.sheetWrap
			});
			this.$el // TODO use rx handlers?
				.on('mouseenter mouseleave', this.mouseenterLeave);

			setTimeout(function () {
				self.setWidth(options.ws.column.width);
			}, 500);
			this.subs.add(this.state.refine({ 'width': ['column_rendering', this.id, 'width'] })
				.subscribe(function (s) {
					self.setWidth(s.width);
				}));

			this.reRender(options);
		},

		render: function (options) {
			if (this.$el) {
				this.reRender(options);
			} else {
				this.firstRender(options);
			}
		},

		kmPlotShow: function () {
			kmPlot.show(this.id, {
				cursor: this.cursor.refine({ 'kmPlot': ['column_rendering', this.id, 'kmPlot'] }),
				columnUi: this
			});
		},

		kmPlotVisibility: function () {
			var myKmState,
				myPlotted,
				self = this;
			this.subs.add(this.state.distinctUntilChanged(function (s) {
				return s.column_rendering[self.id].kmPlot;
			})
				.subscribe(function (s) {
					var kmState = s.column_rendering[self.id].kmPlot,
						plotted = s._column[self.id].plotted;
					if (myKmState && !kmState) {
						myKmState = kmState;
						kmPlot.destroy(self.id);
					}
					if (!myKmState && kmState) {
						myKmState = kmState;
						if (plotted) {
							self.kmPlotShow();
						}
					}
				}));
			this.subs.add(this.state.distinctUntilChanged(function (s) {
				return s._column[self.id].plotted;
			})
				.subscribe(function (s) {
					var kmState = s.column_rendering[self.id].kmPlot,
						plotted = s._column[self.id].plotted;
					if (myPlotted && !plotted) {
						myPlotted = plotted;
					}
					if (!myPlotted && plotted) {
						myPlotted = plotted;
						if (kmState) {
							self.kmPlotShow();
						}
					}
				}));
		},

		initialize: function (options) {
			var self = this;
			_.bindAll.apply(_, [this].concat(_.functions(this)));

			this.subs = new Rx.CompositeDisposable();
			this.sheetWrap = options.sheetWrap;
			this.cursor = options.cursor;
			this.xenaCursor = options.xenaCursor;
			this.state = options.state
				.takeWhile(_.partial(columnExists, this.id))
				.finally(this.destroy)
				.share();
			this.sparsePad = options.sparsePad;
			this.headerPlotHeight = options.headerPlotHeight;
			this.horizontalMargin = options.horizontalMargin.toString() + 'px';

			if (options.ws) {
				this.render(options);
			}
			this.crosshairs = crosshairs.create(this.id, { $anchor: this.$samplePlot });

			this.subs.add(this.$samplePlot.onAsObservable('click')
				.filter(function (ev) {
					return ev.altKey === true;
				})
				.subscribe(tooltip.toggleFreeze));

			this.kmPlotVisibility();
		}
	};

	function create(id, options) {
		var w = Object.create(aWidget);
		w.id = id;
		w.initialize(options);
		return w;
	}

	return {
		show: function (id, options) {
			var widget = widgets[id];
			if (widget) {
				widget.render(options);
			} else {
				widget = widgets[id] = create(id, options);
			}
			return widget;
		}
	};
});
<|endoftext|>


<commit_before>
var CAMERA_MOVE_LENGTH = 2;

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    backgroundCamera.aspect = window.innerWidth / window.innerHeight;
    backgroundCamera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );
}

function onDocumentMouseDown( event ) {
    event.preventDefault();

    if (game) {
        if (INTERSECTED) {
            if (INTERSECTED.id != rollOverMesh.id) {
                // only update if it's a different block from the one we're on right now
                startMovingBlock(INTERSECTED);

            }
            controls.enabled = false;
            MOVINGPIECE = true;
        }
        if (MOVING || MOVINGMAIN) {
            controls.enabled = false;
            MOVINGPIECE = true;
        }
    }

    document.addEventListener('mousemove', onDocumentMouseMove, false);
    document.addEventListener('mouseup', onDocumentMouseUp, false);
    mouseDown = true;
    // if (game) {
    //     if (game.showingPreview) {
    //         game.switchBlock();
    //     }
    // }
}

function onDocumentMouseUp( event ) {
    event.preventDefault();
    mouseDown = false;
    SELECTED = false;
    MOVINGPIECE = null;
    controls.enabled = true;
    controls.resetState();
    if (gameInProgress) {
        if (game.mode != Game.MODE_RANDOM) {
            game.checkSuccess();
        }

        INTERSECTED = null;
        intersectToHighlight();
    }
    document.removeEventListener('mouseup', onDocumentMouseUp, false);

    // console.log(mouseDown);
}

function onDocumentMouseMove( event ) {
    event.preventDefault();
    if (MOVINGPIECE) {
        dragPiece();
    }

    mouse2D.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse2D.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

}

function onDocumentKeyDown( event ) {
    if (!gameInProgress) {
        return;
    }

    var toMove = new THREE.Vector3(0, 0, 0);
    var moved = false;
    var newPos;
    var rotated = false;
    var climbed = 0;
    var climbedTooFar = false;
    var noLegalSpot;

    switch( event.keyCode ) {
        case 49: // NUMBER 1
            rotate( gameBoardOrientation, "yaw" );
            rotated = true;
            break;
        case 50: // NUMBER 2
            rotate( gameBoardOrientation, "pitch" );
            rotated = true;
            break;
        case 51: // NUMBER 3
            rotate( gameBoardOrientation, "roll" );
            rotated = true;
            break;
        case 16:
            isShiftDown = true;
            if (game.boundingBox) {
                game.boundingBox.visible = !game.boundingBox.visible;
            }
            break;
        case 17: isCtrlDown = true; break;
        case 187: isEqualsDown = true; break;
        case 189: isDashDown = true; break;
        case 65:
            aDown = true;
            moveLeft(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 87:
            wDown = true;
            moveForward(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 83:
            sDown = true;
            moveBackward(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 68:
            dDown = true;
            moveRight(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 82:
            rDown = true;
            toMove.y += STEP_SIZE;
            climbed = 1;
            break;
        case 70:
            fDown = true;
            climbed = -1;
            break;
        case 37:
            isLeftDown = true;
            break;
        case 32:
            isSpaceDown = true;
            if (game.mode != Game.MODE_RANDOM) {
                // move the next block
                for (var i = 0; i < game.existingBlocks.length; i++) {
                    if (game.canMoveBlock(game.existingBlocks[i])) {
                        startMovingBlock(game.existingBlocks[i].mesh);
                        break;
                    }
                }
            } else {
                add_voxel();
            }
            break;
        case 27:    // esc
            pauseGame()
            break;
    }

    // // !! only for building levels!!
    // if (climbed) {
    //     rollOverMesh.position.add(toMove);
    //     return;
    // }

    if ( rotated || moved ) {
        newPos = rollOverMesh.position.clone();

        if (moved) {
            newPos.add(toMove);
        }

        moveToLegal(game.currentBlock, newPos);
        setPosition(newPos, rollOverMesh.position);
    }

    if ( climbed == 1) {
        console.log("climbing");
        newPos = rollOverMesh.position.clone();

        climbToLegal(game.currentBlock, newPos);
        if (!climbedTooFar) {
           setPosition(newPos, rollOverMesh.position);
        } else if (climbedTooFar) {
            alert("cannot move in that direction");
        }

    }
    if (climbed == -1) {
        console.log("falling");
        newPos = rollOverMesh.position.clone();
        fallToLegal(game.currentBlock,newPos,noLegalSpot);
        if (!noLegalSpot) {
            setPosition(newPos, rollOverMesh.position);
        }
        else if (noLegalSpot) {
            alert("cannot move in that direction");
        }

    }

    if ( rotated || moved || climbed) {
        if (toCheckGoal) {
            game.checkGoal(moved, rotated, false);
        }

        // check if the intersected block can still be moved
        if (game.mode == Game.MODE_LEVEL) {
            intersectToHighlight();
            game.checkSuccess();
        }
        if (game.mode == Game.MODE_TUTORIAL) {
            game.checkSuccess();
        }
    }
}

function onDocumentKeyUp( event ) {
    switch ( event.keyCode ) {
        case 16: isShiftDown = false; break;
        case 17: isCtrlDown = false; break;
        case 187: isEqualsDown = false; break;
        case 189: isDashDown = false; break;
        case 65: aDown = false; break;
        case 87: wDown = false; break;
        case 83: sDown = false; break;
        case 68: dDown = false; break;
        case 32: isSpaceDown = false; break;
    }
}

function moveLeft( axis, position ) {
    switch ( axis ) {
        case 1: position.x -= STEP_SIZE; break;
        case 2: position.z -= STEP_SIZE; break;
        case 3: position.z += STEP_SIZE; break;
        case 4: position.x += STEP_SIZE; break;
    }
}

function moveRight( axis, position ) {
    switch ( axis ) {
        case 1: position.x += STEP_SIZE; break;
        case 2: position.z += STEP_SIZE; break;
        case 3: position.z -= STEP_SIZE; break;
        case 4: position.x -= STEP_SIZE; break;
    }
}

function moveForward( axis, position ) {
    switch ( axis ) {
        case 1: position.z -= STEP_SIZE; break;
        case 2: position.x += STEP_SIZE; break;
        case 3: position.x -= STEP_SIZE; break;
        case 4: position.z += STEP_SIZE; break;
    }
}

function moveBackward( axis, position ) {
    switch ( axis ) {
        case 1: position.z += STEP_SIZE; break;
        case 2: position.x -= STEP_SIZE; break;
        case 3: position.x += STEP_SIZE; break;
        case 4: position.z -= STEP_SIZE; break;
    }
}

// directions: "pitch", "yaw", "roll"
function rotate( camera_axis, direction ) {
    if ( direction == "yaw" ) {
        game.currentBlock.rotateAroundWorldAxis("y", 90);
    }

    if ( direction == "pitch" ) {
        switch ( camera_axis ) {
            case 1: game.currentBlock.rotateAroundWorldAxis("z",90); break;
            case 2: game.currentBlock.rotateAroundWorldAxis("x",90); break;
            case 3: game.currentBlock.rotateAroundWorldAxis("x",90); break;
            case 4: game.currentBlock.rotateAroundWorldAxis("z",90); break;
        }
    }
    if ( direction == "roll" ) {
        switch ( camera_axis ) {
            case 1: game.currentBlock.rotateAroundWorldAxis("z",-90); break;
            case 2: game.currentBlock.rotateAroundWorldAxis("x",-90); break;
            case 3: game.currentBlock.rotateAroundWorldAxis("x",-90); break;
            case 4: game.currentBlock.rotateAroundWorldAxis("z",-90); break;
        }
    }

}

//assumes that you are in an illegal position
function moveToLegal(block, newPos) {
    while (!block.isPosLegal(newPos)) {
        if (pos_illegal_code == 1) {
            // move up until we're legal
            newPos.y += STEP_SIZE;
        } else if (pos_illegal_code == 2) {
            // make sure we're in bounds
            block.moveIntoBounds(newPos);
        }
    }

    // then make sure we're stuck to the ground
    while(game.currentBlock.isPosLegal(newPos)) {
        newPos.y -= STEP_SIZE;
    }
    newPos.y += STEP_SIZE;
}

function climbToLegal(block, newPos) {
    while (block.isPosLegal(newPos)) {
        // move up until illegal
        newPos.y += STEP_SIZE;
        if (Math.abs(block.mesh.position.y - newPos.y) > 200) {
            climbedTooFar = true;
            while(block.isPosLegal(newPos)) {
                newPos.y -= STEP_SIZE;
            }
            newPos.y += STEP_SIZE;
            return;
        }
    }
    climbToLegalAgain(block,newPos);
    if (pos_illegal_code == 1 || pos_illegal_code == 2) {
        climbedTooFar = true;
    }

}
function climbToLegalAgain(block, newPos) {
    if (block.isPosLegal(newPos)) {
        return;
    }
    while (!block.isPosLegal(newPos)) {
        if (pos_illegal_code == 2) {
            console.log("ran into floor");
            break;
        }
        newPos.y += STEP_SIZE;
    }
    if (pos_illegal_code == 1 || pos_illegal_code == 2) {
        climbedTooFar = true;
    }
}

function fallToLegal(block,newPos,flag) {
    while (block.isPosLegal(newPos)) {
        newPos.y -= STEP_SIZE;

    }

    if (pos_illegal_code == 2) {
        newPos.y += STEP_SIZE
    }
    else {
        fallToLegalAgain(block,newPos,flag);
    }
}

function fallToLegalAgain (block, newPos,flag) {
    console.log(block.isPosLegal(newPos));
    if (block.isPosLegal(newPos)) {
        return;
    }
    while(!block.isPosLegal(newPos)) {
        if (pos_illegal_code == 2) {
            console.log("no legal spots");
            var reGrow = true;
            break;
        }
        newPos.y -= STEP_SIZE;
    }
    console.log("regrow",reGrow);
    if (reGrow) {
        while(!block.isPosLegal(newPos)) {
            newPos.y += STEP_SIZE;
        }
    }
}

function moveTowardsPlayer(oldPos) {
    // placement of the new block - first move towards user's perspective until we can't move anymore
    while (!game.currentBlock.isPosLegal(oldPos)) {
        if (pos_illegal_code == 1) {
            moveBackward(gameBoardOrientation, oldPos);
        } else if (pos_illegal_code == 2) {
            break;
        }
    }

    moveToLegal(game.currentBlock, oldPos);
}

function add_voxel( ) {
    var voxel = rollOverMesh;
    var oldPos = voxel.position.clone();

    // places rollover block down and make it static
    game.currentBlock.makeStatic();

    // update all blocks
    game.addToExisting(game.currentBlock, voxel.position);
    volume_doc.innerHTML = game.totalVolume;

    blockNoise.load();
    blockNoise.play();

    if (toCheckGoal) {
        game.checkGoal(false, false, true);
    }

    // create new block and use that new block as rollover
    game.getNextBlock();

    if (game.currentBlock == null) {
        return;
    }

    rollOverMesh = game.currentBlock.mesh;

    rollOverMesh.position.x = oldPos.x;
    rollOverMesh.position.y = oldPos.y;
    rollOverMesh.position.z = oldPos.z;

    moveTowardsPlayer(rollOverMesh.position);

    scene.add( rollOverMesh );
}

<commit_msg>disable moving pieces when game is not in progress<commit_after>
var CAMERA_MOVE_LENGTH = 2;

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    backgroundCamera.aspect = window.innerWidth / window.innerHeight;
    backgroundCamera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );
}

function onDocumentMouseDown( event ) {
    event.preventDefault();

    if (gameInProgress) {
        if (INTERSECTED) {
            if (INTERSECTED.id != rollOverMesh.id) {
                // only update if it's a different block from the one we're on right now
                startMovingBlock(INTERSECTED);

            }
            controls.enabled = false;
            MOVINGPIECE = true;
        }
        if (MOVING || MOVINGMAIN) {
            controls.enabled = false;
            MOVINGPIECE = true;
        }

        document.addEventListener('mousemove', onDocumentMouseMove, false);
        document.addEventListener('mouseup', onDocumentMouseUp, false);
        mouseDown = true;
    }
}

function onDocumentMouseUp( event ) {
    event.preventDefault();
    mouseDown = false;
    SELECTED = false;
    MOVINGPIECE = null;
    controls.enabled = true;
    controls.resetState();
    if (gameInProgress) {
        if (game.mode != Game.MODE_RANDOM) {
            game.checkSuccess();
        }

        INTERSECTED = null;
        intersectToHighlight();
    }
    document.removeEventListener('mouseup', onDocumentMouseUp, false);

    // console.log(mouseDown);
}

function onDocumentMouseMove( event ) {
    event.preventDefault();
    if (MOVINGPIECE) {
        dragPiece();
    }

    mouse2D.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse2D.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

}

function onDocumentKeyDown( event ) {
    if (!gameInProgress) {
        return;
    }

    var toMove = new THREE.Vector3(0, 0, 0);
    var moved = false;
    var newPos;
    var rotated = false;
    var climbed = 0;
    var climbedTooFar = false;
    var noLegalSpot;

    switch( event.keyCode ) {
        case 49: // NUMBER 1
            rotate( gameBoardOrientation, "yaw" );
            rotated = true;
            break;
        case 50: // NUMBER 2
            rotate( gameBoardOrientation, "pitch" );
            rotated = true;
            break;
        case 51: // NUMBER 3
            rotate( gameBoardOrientation, "roll" );
            rotated = true;
            break;
        case 16:
            isShiftDown = true;
            if (game.boundingBox) {
                game.boundingBox.visible = !game.boundingBox.visible;
            }
            break;
        case 17: isCtrlDown = true; break;
        case 187: isEqualsDown = true; break;
        case 189: isDashDown = true; break;
        case 65:
            aDown = true;
            moveLeft(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 87:
            wDown = true;
            moveForward(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 83:
            sDown = true;
            moveBackward(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 68:
            dDown = true;
            moveRight(gameBoardOrientation, toMove);
            moved = true;
            break;
        case 82:
            rDown = true;
            toMove.y += STEP_SIZE;
            climbed = 1;
            break;
        case 70:
            fDown = true;
            climbed = -1;
            break;
        case 37:
            isLeftDown = true;
            break;
        case 32:
            isSpaceDown = true;
            if (game.mode != Game.MODE_RANDOM) {
                // move the next block
                for (var i = 0; i < game.existingBlocks.length; i++) {
                    if (game.canMoveBlock(game.existingBlocks[i])) {
                        startMovingBlock(game.existingBlocks[i].mesh);
                        break;
                    }
                }
            } else {
                add_voxel();
            }
            break;
        case 27:    // esc
            pauseGame()
            break;
    }

    // // !! only for building levels!!
    // if (climbed) {
    //     rollOverMesh.position.add(toMove);
    //     return;
    // }

    if ( rotated || moved ) {
        newPos = rollOverMesh.position.clone();

        if (moved) {
            newPos.add(toMove);
        }

        moveToLegal(game.currentBlock, newPos);
        setPosition(newPos, rollOverMesh.position);
    }

    if ( climbed == 1) {
        console.log("climbing");
        newPos = rollOverMesh.position.clone();

        climbToLegal(game.currentBlock, newPos);
        if (!climbedTooFar) {
           setPosition(newPos, rollOverMesh.position);
        } else if (climbedTooFar) {
            alert("cannot move in that direction");
        }

    }
    if (climbed == -1) {
        console.log("falling");
        newPos = rollOverMesh.position.clone();
        fallToLegal(game.currentBlock,newPos,noLegalSpot);
        if (!noLegalSpot) {
            setPosition(newPos, rollOverMesh.position);
        }
        else if (noLegalSpot) {
            alert("cannot move in that direction");
        }

    }

    if ( rotated || moved || climbed) {
        if (toCheckGoal) {
            game.checkGoal(moved, rotated, false);
        }

        // check if the intersected block can still be moved
        if (game.mode == Game.MODE_LEVEL) {
            intersectToHighlight();
            game.checkSuccess();
        }
        if (game.mode == Game.MODE_TUTORIAL) {
            game.checkSuccess();
        }
    }
}

function onDocumentKeyUp( event ) {
    switch ( event.keyCode ) {
        case 16: isShiftDown = false; break;
        case 17: isCtrlDown = false; break;
        case 187: isEqualsDown = false; break;
        case 189: isDashDown = false; break;
        case 65: aDown = false; break;
        case 87: wDown = false; break;
        case 83: sDown = false; break;
        case 68: dDown = false; break;
        case 32: isSpaceDown = false; break;
    }
}

function moveLeft( axis, position ) {
    switch ( axis ) {
        case 1: position.x -= STEP_SIZE; break;
        case 2: position.z -= STEP_SIZE; break;
        case 3: position.z += STEP_SIZE; break;
        case 4: position.x += STEP_SIZE; break;
    }
}

function moveRight( axis, position ) {
    switch ( axis ) {
        case 1: position.x += STEP_SIZE; break;
        case 2: position.z += STEP_SIZE; break;
        case 3: position.z -= STEP_SIZE; break;
        case 4: position.x -= STEP_SIZE; break;
    }
}

function moveForward( axis, position ) {
    switch ( axis ) {
        case 1: position.z -= STEP_SIZE; break;
        case 2: position.x += STEP_SIZE; break;
        case 3: position.x -= STEP_SIZE; break;
        case 4: position.z += STEP_SIZE; break;
    }
}

function moveBackward( axis, position ) {
    switch ( axis ) {
        case 1: position.z += STEP_SIZE; break;
        case 2: position.x -= STEP_SIZE; break;
        case 3: position.x += STEP_SIZE; break;
        case 4: position.z -= STEP_SIZE; break;
    }
}

// directions: "pitch", "yaw", "roll"
function rotate( camera_axis, direction ) {
    if ( direction == "yaw" ) {
        game.currentBlock.rotateAroundWorldAxis("y", 90);
    }

    if ( direction == "pitch" ) {
        switch ( camera_axis ) {
            case 1: game.currentBlock.rotateAroundWorldAxis("z",90); break;
            case 2: game.currentBlock.rotateAroundWorldAxis("x",90); break;
            case 3: game.currentBlock.rotateAroundWorldAxis("x",90); break;
            case 4: game.currentBlock.rotateAroundWorldAxis("z",90); break;
        }
    }
    if ( direction == "roll" ) {
        switch ( camera_axis ) {
            case 1: game.currentBlock.rotateAroundWorldAxis("z",-90); break;
            case 2: game.currentBlock.rotateAroundWorldAxis("x",-90); break;
            case 3: game.currentBlock.rotateAroundWorldAxis("x",-90); break;
            case 4: game.currentBlock.rotateAroundWorldAxis("z",-90); break;
        }
    }

}

//assumes that you are in an illegal position
function moveToLegal(block, newPos) {
    while (!block.isPosLegal(newPos)) {
        if (pos_illegal_code == 1) {
            // move up until we're legal
            newPos.y += STEP_SIZE;
        } else if (pos_illegal_code == 2) {
            // make sure we're in bounds
            block.moveIntoBounds(newPos);
        }
    }

    // then make sure we're stuck to the ground
    while(game.currentBlock.isPosLegal(newPos)) {
        newPos.y -= STEP_SIZE;
    }
    newPos.y += STEP_SIZE;
}

function climbToLegal(block, newPos) {
    while (block.isPosLegal(newPos)) {
        // move up until illegal
        newPos.y += STEP_SIZE;
        if (Math.abs(block.mesh.position.y - newPos.y) > 200) {
            climbedTooFar = true;
            while(block.isPosLegal(newPos)) {
                newPos.y -= STEP_SIZE;
            }
            newPos.y += STEP_SIZE;
            return;
        }
    }
    climbToLegalAgain(block,newPos);
    if (pos_illegal_code == 1 || pos_illegal_code == 2) {
        climbedTooFar = true;
    }

}
function climbToLegalAgain(block, newPos) {
    if (block.isPosLegal(newPos)) {
        return;
    }
    while (!block.isPosLegal(newPos)) {
        if (pos_illegal_code == 2) {
            console.log("ran into floor");
            break;
        }
        newPos.y += STEP_SIZE;
    }
    if (pos_illegal_code == 1 || pos_illegal_code == 2) {
        climbedTooFar = true;
    }
}

function fallToLegal(block,newPos,flag) {
    while (block.isPosLegal(newPos)) {
        newPos.y -= STEP_SIZE;

    }

    if (pos_illegal_code == 2) {
        newPos.y += STEP_SIZE
    }
    else {
        fallToLegalAgain(block,newPos,flag);
    }
}

function fallToLegalAgain (block, newPos,flag) {
    console.log(block.isPosLegal(newPos));
    if (block.isPosLegal(newPos)) {
        return;
    }
    while(!block.isPosLegal(newPos)) {
        if (pos_illegal_code == 2) {
            console.log("no legal spots");
            var reGrow = true;
            break;
        }
        newPos.y -= STEP_SIZE;
    }
    console.log("regrow",reGrow);
    if (reGrow) {
        while(!block.isPosLegal(newPos)) {
            newPos.y += STEP_SIZE;
        }
    }
}

function moveTowardsPlayer(oldPos) {
    // placement of the new block - first move towards user's perspective until we can't move anymore
    while (!game.currentBlock.isPosLegal(oldPos)) {
        if (pos_illegal_code == 1) {
            moveBackward(gameBoardOrientation, oldPos);
        } else if (pos_illegal_code == 2) {
            break;
        }
    }

    moveToLegal(game.currentBlock, oldPos);
}

function add_voxel( ) {
    var voxel = rollOverMesh;
    var oldPos = voxel.position.clone();

    // places rollover block down and make it static
    game.currentBlock.makeStatic();

    // update all blocks
    game.addToExisting(game.currentBlock, voxel.position);
    volume_doc.innerHTML = game.totalVolume;

    blockNoise.load();
    blockNoise.play();

    if (toCheckGoal) {
        game.checkGoal(false, false, true);
    }

    // create new block and use that new block as rollover
    game.getNextBlock();

    if (game.currentBlock == null) {
        return;
    }

    rollOverMesh = game.currentBlock.mesh;

    rollOverMesh.position.x = oldPos.x;
    rollOverMesh.position.y = oldPos.y;
    rollOverMesh.position.z = oldPos.z;

    moveTowardsPlayer(rollOverMesh.position);

    scene.add( rollOverMesh );
}

<|endoftext|>


<commit_before>/**
 Copyright 2014 Gordon Williams (gw@pur3.co.uk)

 This Source Code is subject to the terms of the Mozilla Public
 License, v2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 ------------------------------------------------------------------
  Main App Container
 ------------------------------------------------------------------
**/
"use strict";

(function() {

  var defaultIcon = {
    area: {
      name: "toolbar",
      position: "right"
    },
    menu: []
  };
  var initialised = false;

  /**
   * Initialize the window
   */
  function init()
  {
    // Hookup window buttons
    Espruino.Core.App.addIcon({
      id:'minimize',
      icon: 'minus',
      title: 'Minimize Window',
      order: 1000,
      cssClass: 'title-bar__button--minimize',
      area: {
        name: "titlebar",
        position: "right"
      },
      click: function(){
        chrome.app.window.current().minimize();
      }
    });

    Espruino.Core.App.addIcon({
      id:'maximize',
      icon: 'window',
      title: 'Maximize / Restore Window',
      order: 1001,
      cssClass: 'title-bar__button--maximize',
      area: {
        name: "titlebar",
        position: "right"
      },
      click: function(){
        if(chrome.app.window.current().isMaximized()) {
          chrome.app.window.current().restore();
        } else {
          chrome.app.window.current().maximize();
        }
      }
    });

    Espruino.Core.App.addIcon({
      id:'close',
      icon: 'cross',
      title: 'Close Window',
      order: 1002,
      cssClass: 'title-bar__button--close',
      area: {
        name: "titlebar",
        position: "right"
      },
      click: function(){
        if(Espruino.Core.Serial.isConnected){
          Espruino.Core.Serial.close(function(){
            chrome.app.window.current().close();
          });
        } else {
          chrome.app.window.current().close();
        }
      }
    });

    // Setup splitter
    $(".split-pane").splitster({
      orientation: "vertical", //TODO: Load from local storage,
      barWidth: 0, // Don't show the bar when vertical,
      draggable: ".editor--code > .sidebar"
    });

    // Setup orientation button
    var orientation = "vertical";
    var orientationBtn = Espruino.Core.App.addIcon({
      id: "orientation",
      icon: "split-" + orientation,
      title : "Toggle Orientation",
      order: -80,
      divider: "right",
      area: {
        name: "toolbar",
        position: "right"
      },
      click: function() {
        orientation = orientation == "vertical" ? "horizontal" : "vertical";
        $(".split-pane").splitster("orientation", orientation);
        $(".split-pane").splitster("barWidth", orientation == "vertical" ? 0 : 10);
        $(".split-pane").splitster("draggable", orientation == "vertical" ? ".editor--code > .sidebar" : false);
        orientationBtn.setIcon("split-" + orientation);
      }
    });

    // layout after everything else has been added
    Espruino.addProcessor("initialised", function(data,callback) {
      sortIcons();
      callback(data);
      initialised = true;
    });
  }

  // sort all icons in a container according to their icon-order field (see addIcon)
  function sortIcons(container)
  {
    if (container === undefined) {
      sortIcons(".title-bar__buttons");
      sortIcons(".toolbar__buttons--left");
      sortIcons(".toolbar__buttons--right");
      sortIcons(".editor--terminal .sidebar__buttons--top");
      sortIcons(".editor--terminal .sidebar__buttons--bottom");
      sortIcons(".editor--code .sidebar__buttons--top");
      sortIcons(".editor--code .sidebar__buttons--bottom");
      return;
    }

    var mylist = typeof container === "string" ? $(container) : container;
    var listitems = mylist.children(/*'a'*/).get();
    listitems.sort(function(a, b) {
       return parseFloat($(a).data("icon-order")) - parseFloat($(b).data("icon-order"));
    });
    $.each(listitems, function(idx, itm) { mylist.append(itm); });
  }

  /**
   * Close a popup window if one was shown
   */
  function closePopup()
  {
    var api = $(".window--modal").data("api");
    if(api)
        api.close();
  }

  /**
   * Open a popup window
   */
  function openPopup(options)
  {
    // Declare API first, as we need to make sure the close button / overlay click
    // call the methods on the API object, rathert than a copy of the close method
    // so that the close method can be overridden with extra logic if needed.
    var api = {
      setContents : function(contents)
      {
        $(".window--modal > .window__viewport").html(contents);
      },
      close : function(){
        $(".window__overlay").remove();
      }
    }

    // Append the modal overlay
    $('

').appendTo(".window--app > .window__viewport").click(function(){
      api.close();
    });

    // Append the popup window
    $('
'+
          '
'+
            '
'+ options.title +'
'+
            '

'+
          '
'+
          '
'+
            (options.padding ? '
':'')+
            options.contents +
            (options.padding ? '
':'')+
          '
'+
        '
').appendTo(".window__overlay-inner").click(function(e){ e.stopPropagation(); })

    // Append close button
    $('').appendTo(".window--modal .title-bar__buttons").click(function(){
      api.close();
    });

    // Append 'next' button if we registered a callback
    if (options.next) {
      // TODO - nasty - need nice CSS
      $('
Next
')
        .appendTo(".window--modal .window__viewport").click(options.next);
    }

    // Apply dimensions
    if(options.width)
    {
      $(".window--modal").width(options.width);
    }

    if(options.height)
    {
      $(".window--modal").height(options.height);
    }

    $(".window--modal").data("api", api);

    return api;
  }

  /**
   * Add an icon to the window in the specified area
   *
   * options = {
   *   area : splitter | left | middle | right
   *   name : icon name - corresponds to icons.css
   *   title : nice title for tooltips
   *   order : integer specifying the order. After icons have been added they'll be sorted so this is ascending
   */
  function addIcon(options)
  {
    options = $.extend({}, defaultIcon, options);

    var selector = "";
    var iconSize = 'lrg';
    var additionalClasses = '';
    switch(options.area.name){
      case "titlebar":
        selector = ".title-bar__buttons";
        iconSize = 'sml';
        additionalClasses = 'title-bar__button';
        break;
      case "toolbar":
        selector = ".toolbar__buttons--" + options.area.position
        break;
      case "terminal":
        selector = ".editor--terminal .sidebar__buttons--" + options.area.position
        break;
      case "code":
        selector = ".editor--code .sidebar__buttons--" + options.area.position
        break;
    }

    if(options.cssClass)
      additionalClasses += ' '+ options.cssClass;

    var container = $(selector);
    if(container.length == 0)
    {
      console.warn("App.addIcon unknown area: "+ selector);
      return;
    }

    var order = 0;
    if (options.order !== undefined)
      order = options.order;

    var elementClass = 'icon-'+ options.icon;
    var element = $('').appendTo(container);

    if(options.divider)
      element.addClass("icon--divide-"+ options.divider);

    if(options.click)
      element.on("click", options.click);

    if (initialised)
      sortIcons(selector);

    var api = {
      setIcon : function(icon) {
        element.removeClass(elementClass);
        elementClass = 'icon-'+ icon;
        element.addClass(elementClass);
      },
      remove : function() {
        element.off(); // Remove all event handlers
        element.remove();
      },
      addMenuItem: function(options)
      {
        var menuEl = element.find(".menu");
        if(menuEl.length == 0)
           menuEl = $('

').appendTo(element)

        var order = 0;
        if (options.order !== undefined)
          order = options.order;

        var menuItemEl = $(' '+ options.title +'').appendTo(menuEl);
        if(options.click)
          menuItemEl.click(function(e){
            e.stopPropagation();
            options.click(e);
          });

        sortIcons(menuEl);
      }
    };

    if(options.menu && options.menu.length > 0)
    {
      $.each(options.menu, function(idx, itm){
        api.addMenuItem(itm);
      });
    }

    element.data("api", api);

    return api;
  }

  function findIcon(id)
  {
    return $("#icon-"+ id).data("api");
  }

  Espruino.Core.App = {
      init : init,
      openPopup: openPopup,
      closePopup: closePopup,
      addIcon: addIcon,
      findIcon: findIcon
  };

})();
<commit_msg>fix close button<commit_after>/**
 Copyright 2014 Gordon Williams (gw@pur3.co.uk)

 This Source Code is subject to the terms of the Mozilla Public
 License, v2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 ------------------------------------------------------------------
  Main App Container
 ------------------------------------------------------------------
**/
"use strict";

(function() {

  var defaultIcon = {
    area: {
      name: "toolbar",
      position: "right"
    },
    menu: []
  };
  var initialised = false;

  /**
   * Initialize the window
   */
  function init()
  {
    // Hookup window buttons
    Espruino.Core.App.addIcon({
      id:'minimize',
      icon: 'minus',
      title: 'Minimize Window',
      order: 1000,
      cssClass: 'title-bar__button--minimize',
      area: {
        name: "titlebar",
        position: "right"
      },
      click: function(){
        chrome.app.window.current().minimize();
      }
    });

    Espruino.Core.App.addIcon({
      id:'maximize',
      icon: 'window',
      title: 'Maximize / Restore Window',
      order: 1001,
      cssClass: 'title-bar__button--maximize',
      area: {
        name: "titlebar",
        position: "right"
      },
      click: function(){
        if(chrome.app.window.current().isMaximized()) {
          chrome.app.window.current().restore();
        } else {
          chrome.app.window.current().maximize();
        }
      }
    });

    Espruino.Core.App.addIcon({
      id:'close',
      icon: 'cross',
      title: 'Close Window',
      order: 1002,
      cssClass: 'title-bar__button--close',
      area: {
        name: "titlebar",
        position: "right"
      },
      click: function(){
        if(Espruino.Core.Serial.isConnected()){
          Espruino.Core.Serial.close(function(){
            chrome.app.window.current().close();
          });
        } else {
          chrome.app.window.current().close();
        }
      }
    });

    // Setup splitter
    $(".split-pane").splitster({
      orientation: "vertical", //TODO: Load from local storage,
      barWidth: 0, // Don't show the bar when vertical,
      draggable: ".editor--code > .sidebar"
    });

    // Setup orientation button
    var orientation = "vertical";
    var orientationBtn = Espruino.Core.App.addIcon({
      id: "orientation",
      icon: "split-" + orientation,
      title : "Toggle Orientation",
      order: -80,
      divider: "right",
      area: {
        name: "toolbar",
        position: "right"
      },
      click: function() {
        orientation = orientation == "vertical" ? "horizontal" : "vertical";
        $(".split-pane").splitster("orientation", orientation);
        $(".split-pane").splitster("barWidth", orientation == "vertical" ? 0 : 10);
        $(".split-pane").splitster("draggable", orientation == "vertical" ? ".editor--code > .sidebar" : false);
        orientationBtn.setIcon("split-" + orientation);
      }
    });

    // layout after everything else has been added
    Espruino.addProcessor("initialised", function(data,callback) {
      sortIcons();
      callback(data);
      initialised = true;
    });
  }

  // sort all icons in a container according to their icon-order field (see addIcon)
  function sortIcons(container)
  {
    if (container === undefined) {
      sortIcons(".title-bar__buttons");
      sortIcons(".toolbar__buttons--left");
      sortIcons(".toolbar__buttons--right");
      sortIcons(".editor--terminal .sidebar__buttons--top");
      sortIcons(".editor--terminal .sidebar__buttons--bottom");
      sortIcons(".editor--code .sidebar__buttons--top");
      sortIcons(".editor--code .sidebar__buttons--bottom");
      return;
    }

    var mylist = typeof container === "string" ? $(container) : container;
    var listitems = mylist.children(/*'a'*/).get();
    listitems.sort(function(a, b) {
       return parseFloat($(a).data("icon-order")) - parseFloat($(b).data("icon-order"));
    });
    $.each(listitems, function(idx, itm) { mylist.append(itm); });
  }

  /**
   * Close a popup window if one was shown
   */
  function closePopup()
  {
    var api = $(".window--modal").data("api");
    if(api)
        api.close();
  }

  /**
   * Open a popup window
   */
  function openPopup(options)
  {
    // Declare API first, as we need to make sure the close button / overlay click
    // call the methods on the API object, rathert than a copy of the close method
    // so that the close method can be overridden with extra logic if needed.
    var api = {
      setContents : function(contents)
      {
        $(".window--modal > .window__viewport").html(contents);
      },
      close : function(){
        $(".window__overlay").remove();
      }
    }

    // Append the modal overlay
    $('

').appendTo(".window--app > .window__viewport").click(function(){
      api.close();
    });

    // Append the popup window
    $('
'+
          '
'+
            '
'+ options.title +'
'+
            '

'+
          '
'+
          '
'+
            (options.padding ? '
':'')+
            options.contents +
            (options.padding ? '
':'')+
          '
'+
        '
').appendTo(".window__overlay-inner").click(function(e){ e.stopPropagation(); })

    // Append close button
    $('').appendTo(".window--modal .title-bar__buttons").click(function(){
      api.close();
    });

    // Append 'next' button if we registered a callback
    if (options.next) {
      // TODO - nasty - need nice CSS
      $('
Next
')
        .appendTo(".window--modal .window__viewport").click(options.next);
    }

    // Apply dimensions
    if(options.width)
    {
      $(".window--modal").width(options.width);
    }

    if(options.height)
    {
      $(".window--modal").height(options.height);
    }

    $(".window--modal").data("api", api);

    return api;
  }

  /**
   * Add an icon to the window in the specified area
   *
   * options = {
   *   area : splitter | left | middle | right
   *   name : icon name - corresponds to icons.css
   *   title : nice title for tooltips
   *   order : integer specifying the order. After icons have been added they'll be sorted so this is ascending
   */
  function addIcon(options)
  {
    options = $.extend({}, defaultIcon, options);

    var selector = "";
    var iconSize = 'lrg';
    var additionalClasses = '';
    switch(options.area.name){
      case "titlebar":
        selector = ".title-bar__buttons";
        iconSize = 'sml';
        additionalClasses = 'title-bar__button';
        break;
      case "toolbar":
        selector = ".toolbar__buttons--" + options.area.position
        break;
      case "terminal":
        selector = ".editor--terminal .sidebar__buttons--" + options.area.position
        break;
      case "code":
        selector = ".editor--code .sidebar__buttons--" + options.area.position
        break;
    }

    if(options.cssClass)
      additionalClasses += ' '+ options.cssClass;

    var container = $(selector);
    if(container.length == 0)
    {
      console.warn("App.addIcon unknown area: "+ selector);
      return;
    }

    var order = 0;
    if (options.order !== undefined)
      order = options.order;

    var elementClass = 'icon-'+ options.icon;
    var element = $('').appendTo(container);

    if(options.divider)
      element.addClass("icon--divide-"+ options.divider);

    if(options.click)
      element.on("click", options.click);

    if (initialised)
      sortIcons(selector);

    var api = {
      setIcon : function(icon) {
        element.removeClass(elementClass);
        elementClass = 'icon-'+ icon;
        element.addClass(elementClass);
      },
      remove : function() {
        element.off(); // Remove all event handlers
        element.remove();
      },
      addMenuItem: function(options)
      {
        var menuEl = element.find(".menu");
        if(menuEl.length == 0)
           menuEl = $('

').appendTo(element)

        var order = 0;
        if (options.order !== undefined)
          order = options.order;

        var menuItemEl = $(' '+ options.title +'').appendTo(menuEl);
        if(options.click)
          menuItemEl.click(function(e){
            e.stopPropagation();
            options.click(e);
          });

        sortIcons(menuEl);
      }
    };

    if(options.menu && options.menu.length > 0)
    {
      $.each(options.menu, function(idx, itm){
        api.addMenuItem(itm);
      });
    }

    element.data("api", api);

    return api;
  }

  function findIcon(id)
  {
    return $("#icon-"+ id).data("api");
  }

  Espruino.Core.App = {
      init : init,
      openPopup: openPopup,
      closePopup: closePopup,
      addIcon: addIcon,
      findIcon: findIcon
  };

})();
<|endoftext|>