<commit_before>/**
 * @file 配置文件
 * @author 吴钦飞(wuqf@pkusoft.net)
 */

// namespace
window[ "www.pkusoft.net" ] = {};

// 组件别名（模块ID）
seajs.config( {

    // 别名配置
    alias: {
        // "seajs-css": "lib/sea-modules/seajs-css/1.0.0/seajs-css",
        "seajs-text": "lib/sea-modules/seajs-text/1.1.1/seajs-text",

        // polyfill ES5 grammar
        "es5-shim": "lib/es5-shim/4.5.9.x/es5-shim",
        "es5-sham": "lib/es5-shim/4.5.9.x/es5-sham",

        // polyfill JSON（JSON.parse、JSON.stringify）
        "json3": "lib/json3/3.3.2/json3",

        // polyfill for min/max-width CSS3 Media Queries (for IE 6-8, and more)
        "respond": "lib/respond/1.4.2/respond",

        // 是IE8识别HTML5标记
        "html5shiv": "lib/html5shiv/3.7.3/html5shiv",

        // jQuery
        "jquery": "lib/jquery/1.11.3.x/jquery",

        // jQuery UI
        "jquery-ui": "lib/jquery/plugin/jquery-ui/1.12.1.x/jquery-ui.min",

        // 轮播图组件
        "swiper": "lib/swiper/2.7.6.x/swiper",

        // 弹窗组件
        "layer": "lib/layer/3.0.1.x/layer",

        // 模块名"artDialog"已在模块内定义，请勿更改
        "artDialog": "lib/art-dialog/6.0.4.x/dialog-plus",

        // 模板引擎
        "artTemplate": "lib/art-template/3.1.0/template",

        // RSA
        "jsencrypt": "lib/jsencrypt/2.3.1.x/jsencrypt",

        // AOP
        "meld": "lib/meld/1.3.1.x/meld.js",

        // 表格列宽，拖拽调整（被 bootgrid 依赖）
        "colResizable": "lib/jquery/plugin/colResizable/1.6.0/colResizable",

        // bootstrap dropdown menu（被 bootgrid 依赖）
        "bootstrap-dropdown": "lib/jquery/plugin/bootstrap/3.3.6/dropdown",

        // bootgrid
        "bootgrid": "lib/bootgrid/1.3.1.x/jquery.bootgrid",

        // loading
        "loading": "lib/loading/1.2.0.x/loading.js",

        // 数据源
        "dataSource": "component/common/dataSource/1.0.0/dataSource",

        // 抽屉式弹窗
        "drawer": "component/common/drawer/1.0.0/drawer"

    }
} );

// 其他的配置
+function ( window ) {

    var ns = window[ "www.pkusoft.net" ],
        isSupportHtml5Markup,
        isSupportMediaQuery,
        isSupportES5,
        isSupportJSON,
        isIE8,

        /** 时间戳：版本控制，避免缓存 */
        timestamp,

        scriptNodeList,
        currentScript,
        currentScriptPath,
        pkuiBasePath,
        mainJsPath,

        ctxPath
        ;

    scriptNodeList = window.document[ "scripts" ];
    currentScript = scriptNodeList[ scriptNodeList.length - 1 ];
    currentScriptPath = currentScript.src;
    pkuiBasePath = currentScriptPath.substring( 0, currentScriptPath.lastIndexOf( "/" ) );
    mainJsPath = currentScript.getAttribute( "data-main" );
    timestamp = currentScript.getAttribute( "data-timestamp" );

    if ( !timestamp ) { // 没设置
        timestamp = "v=please-set-version";
    } else if ( timestamp == "dev" ) { // 开发模式
        timestamp = "v=" + new Date().getTime();
    } else if ( timestamp.indexOf( "=" ) === -1 ) { // 生产模式：但没有请求参数名
        timestamp = "v=" + timestamp;
    }

    isSupportHtml5Markup = ( function ( ver ) {
        var b = document.createElement( "b" );
        b.innerHTML = "";
        return b.getElementsByTagName( "i" ).length === 1
    }( 8 ) );

    isIE8 = isSupportHtml5Markup;

    isSupportMediaQuery = window.matchMedia && window.matchMedia( "only all" ) !== null
        && window.matchMedia( "only all" ).matches;

    isSupportES5 = !!Function.prototype.bind;

    isSupportJSON = window.JSON;

    ctxPath = pkuiBasePath.substring( 0, pkuiBasePath.lastIndexOf( "/static" ) );

    // 配置
    seajs.config( {

        // 路径配置
        paths: {
            //"pkui": "../../pkui"
        },

        // 变量配置
        vars: {
            //"locale": "zh-cn"
        },

        // 映射配置
        map: [
            // [ "http://example.com/js/app/", "http://localhost/js/app/" ]
            [ /^(.*\.(?:css|js|tpl))(.*)$/i, '$1?' + timestamp ]
        ],

        // 预加载项
        preload: [
            "jquery"
            //isSupportES5 ? "" : "es5-sham",
            //isSupportJSON ? "" : "json3",
            //isSupportMediaQuery ? "" : "respond",
        ],

        // 调试模式
        debug: true,

        // Sea.js 的基础路径，由页面指定
        base: pkuiBasePath,

        // 文件编码
        charset: "utf-8"
    } );

    if ( mainJsPath ) {
        seajs.use( mainJsPath );
    } else {
        console.info( "“”，没有通过“data-main”指定入口文件URL。" );
    }

    if ( isIE8 ) {
        document.write( "" );
        console.info( "O(∩_∩)O~[ IE8 ]：载入 html5shiv.js" );
    }

    // 暴露到名称空间，
    ns.pkuiBasePath = pkuiBasePath;
    ns.ctxPath = ctxPath;
    ns.timestamp = timestamp;
    ns.isIE8 = isIE8;
    ns.isSupportES5 = isSupportES5;
    ns.isSupportJSON = isSupportJSON;
    ns.isSupportMediaQuery = isSupportMediaQuery;

}( window );

/* function loadJS( src, callback ) { var script = document.createElement( 'script' ); var head = document.getElementsByTagName( 'head' )[ 0 ]; var isLoaded; script.src = src; script.charset = "utf-8"; script.onload = script.onreadystatechange = function () { if ( !isLoaded && (!script.readyState || /loaded|complete/.test( script.readyState )) ) { script.onload = script.onreadystatechange = null; isLoaded = true; callback(); } }; head.appendChild( script ); }*/<commit_msg>【PKUI】config：配置isloading<commit_after>/**
 * @file 配置文件
 * @author 吴钦飞(wuqf@pkusoft.net)
 */

// namespace
window[ "www.pkusoft.net" ] = {};

// 组件别名（模块ID）
seajs.config( {

    // 别名配置
    alias: {
        // "seajs-css": "lib/sea-modules/seajs-css/1.0.0/seajs-css",
        "seajs-text": "lib/sea-modules/seajs-text/1.1.1/seajs-text",

        // polyfill ES5 grammar
        "es5-shim": "lib/es5-shim/4.5.9.x/es5-shim",
        "es5-sham": "lib/es5-shim/4.5.9.x/es5-sham",

        // polyfill JSON（JSON.parse、JSON.stringify）
        "json3": "lib/json3/3.3.2/json3",

        // polyfill for min/max-width CSS3 Media Queries (for IE 6-8, and more)
        "respond": "lib/respond/1.4.2/respond",

        // 是IE8识别HTML5标记
        "html5shiv": "lib/html5shiv/3.7.3/html5shiv",

        // jQuery
        "jquery": "lib/jquery/1.11.3.x/jquery",

        // jQuery UI
        "jquery-ui": "lib/jquery/plugin/jquery-ui/1.12.1.x/jquery-ui.min",

        // 轮播图组件
        "swiper": "lib/swiper/2.7.6.x/swiper",

        // 弹窗组件
        "layer": "lib/layer/3.0.1.x/layer",

        // 模块名"artDialog"已在模块内定义，请勿更改
        "artDialog": "lib/art-dialog/6.0.4.x/dialog-plus",

        // 模板引擎
        "artTemplate": "lib/art-template/3.1.0/template",

        // RSA
        "jsencrypt": "lib/jsencrypt/2.3.1.x/jsencrypt",

        // AOP
        "meld": "lib/meld/1.3.1.x/meld.js",

        // 表格列宽，拖拽调整（被 bootgrid 依赖）
        "colResizable": "lib/jquery/plugin/colResizable/1.6.0/colResizable",

        // bootstrap dropdown menu（被 bootgrid 依赖）
        "bootstrap-dropdown": "lib/jquery/plugin/bootstrap/3.3.6/dropdown",

        // bootgrid
        "bootgrid": "lib/bootgrid/1.3.1.x/jquery.bootgrid",

        // loading
        "isLoading": "lib/isloading/1.0.6.x/isloading",

        // 数据源
        "dataSource": "component/common/dataSource/1.0.0/dataSource",

        // 抽屉式弹窗
        "drawer": "component/common/drawer/1.0.0/drawer"

    }
} );

// 其他的配置
+function ( window ) {

    var ns = window[ "www.pkusoft.net" ],
        isSupportHtml5Markup,
        isSupportMediaQuery,
        isSupportES5,
        isSupportJSON,
        isIE8,

        /** 时间戳：版本控制，避免缓存 */
        timestamp,

        scriptNodeList,
        currentScript,
        currentScriptPath,
        pkuiBasePath,
        mainJsPath,

        ctxPath
        ;

    scriptNodeList = window.document[ "scripts" ];
    currentScript = scriptNodeList[ scriptNodeList.length - 1 ];
    currentScriptPath = currentScript.src;
    pkuiBasePath = currentScriptPath.substring( 0, currentScriptPath.lastIndexOf( "/" ) );
    mainJsPath = currentScript.getAttribute( "data-main" );
    timestamp = currentScript.getAttribute( "data-timestamp" );

    if ( !timestamp ) { // 没设置
        timestamp = "v=please-set-version";
    } else if ( timestamp == "dev" ) { // 开发模式
        timestamp = "v=" + new Date().getTime();
    } else if ( timestamp.indexOf( "=" ) === -1 ) { // 生产模式：但没有请求参数名
        timestamp = "v=" + timestamp;
    }

    isSupportHtml5Markup = ( function ( ver ) {
        var b = document.createElement( "b" );
        b.innerHTML = "";
        return b.getElementsByTagName( "i" ).length === 1
    }( 8 ) );

    isIE8 = isSupportHtml5Markup;

    isSupportMediaQuery = window.matchMedia && window.matchMedia( "only all" ) !== null
        && window.matchMedia( "only all" ).matches;

    isSupportES5 = !!Function.prototype.bind;

    isSupportJSON = window.JSON;

    ctxPath = pkuiBasePath.substring( 0, pkuiBasePath.lastIndexOf( "/static" ) );

    // 配置
    seajs.config( {

        // 路径配置
        paths: {
            //"pkui": "../../pkui"
        },

        // 变量配置
        vars: {
            //"locale": "zh-cn"
        },

        // 映射配置
        map: [
            // [ "http://example.com/js/app/", "http://localhost/js/app/" ]
            [ /^(.*\.(?:css|js|tpl))(.*)$/i, '$1?' + timestamp ]
        ],

        // 预加载项
        preload: [
            "jquery"
            //isSupportES5 ? "" : "es5-sham",
            //isSupportJSON ? "" : "json3",
            //isSupportMediaQuery ? "" : "respond",
        ],

        // 调试模式
        debug: true,

        // Sea.js 的基础路径，由页面指定
        base: pkuiBasePath,

        // 文件编码
        charset: "utf-8"
    } );

    if ( mainJsPath ) {
        seajs.use( mainJsPath );
    } else {
        console.info( "“”，没有通过“data-main”指定入口文件URL。" );
    }

    if ( isIE8 ) {
        document.write( "" );
        console.info( "O(∩_∩)O~[ IE8 ]：载入 html5shiv.js" );
    }

    // 暴露到名称空间，
    ns.pkuiBasePath = pkuiBasePath;
    ns.ctxPath = ctxPath;
    ns.timestamp = timestamp;
    ns.isIE8 = isIE8;
    ns.isSupportES5 = isSupportES5;
    ns.isSupportJSON = isSupportJSON;
    ns.isSupportMediaQuery = isSupportMediaQuery;

}( window );

/* function loadJS( src, callback ) { var script = document.createElement( 'script' ); var head = document.getElementsByTagName( 'head' )[ 0 ]; var isLoaded; script.src = src; script.charset = "utf-8"; script.onload = script.onreadystatechange = function () { if ( !isLoaded && (!script.readyState || /loaded|complete/.test( script.readyState )) ) { script.onload = script.onreadystatechange = null; isLoaded = true; callback(); } }; head.appendChild( script ); }*/<|endoftext|>


<commit_before>
/**
 * body.js
 *
 * Body interface provides common methods for Request and Response
 */

import Stream from 'stream';

import Blob, { BUFFER } from './blob.js';
import FetchError from './fetch-error.js';

let convert;
try { convert = require('encoding').convert; } catch(e) {}

const INTERNALS = Symbol('Body internals');

// fix an issue where "PassThrough" isn't a named export for node <10
const PassThrough = Stream.PassThrough;

/**
 * Body mixin
 *
 * Ref: https://fetch.spec.whatwg.org/#body
 *
 * @param   Stream  body  Readable stream
 * @param   Object  opts  Response options
 * @return  Void
 */
export default function Body(body, {
	size = 0,
	timeout = 0
} = {}) {
	if (body == null) {
		// body is undefined or null
		body = null;
	} else if (isURLSearchParams(body)) {
		// body is a URLSearchParams
		body = Buffer.from(body.toString());
	} else if (body instanceof Blob) {
		// body is blob
		body = body[BUFFER];
	} else if (Buffer.isBuffer(body)) {
		// body is Buffer
	} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
		// body is ArrayBuffer
		body = Buffer.from(body);
	} else if (ArrayBuffer.isView(body)) {
		// body is ArrayBufferView
		body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
	} else if (body instanceof Stream) {
		// body is stream
	} else {
		// none of the above
		// coerce to string then buffer
		body = Buffer.from(String(body));
	}
	this[INTERNALS] = {
		body,
		disturbed: false,
		error: null
	};
	this.size = size;
	this.timeout = timeout;

	if (body instanceof Stream) {
		body.on('error', err => {
			const error = err.name === 'AbortError'
				? err
				: new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err);
			this[INTERNALS].error = error;
		});
	}
}

Body.prototype = {
	get body() {
		return this[INTERNALS].body;
	},

	get bodyUsed() {
		return this[INTERNALS].disturbed;
	},

	/**
	 * Decode response as ArrayBuffer
	 *
	 * @return  Promise
	 */
	arrayBuffer() {
		return consumeBody.call(this).then(buf => buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
	},

	/**
	 * Return raw response as Blob
	 *
	 * @return Promise
	 */
	blob() {
		let ct = this.headers && this.headers.get('content-type') || '';
		return consumeBody.call(this).then(buf => Object.assign(
			// Prevent copying
			new Blob([], {
				type: ct.toLowerCase()
			}),
			{
				[BUFFER]: buf
			}
		));
	},

	/**
	 * Decode response as json
	 *
	 * @return  Promise
	 */
	json() {
		return consumeBody.call(this).then((buffer) => {
			try {
				return JSON.parse(buffer.toString());
			} catch (err) {
				return Body.Promise.reject(new FetchError(`invalid json response body at ${this.url} reason: ${err.message}`, 'invalid-json'));
			}
		})
	},

	/**
	 * Decode response as text
	 *
	 * @return  Promise
	 */
	text() {
		return consumeBody.call(this).then(buffer => buffer.toString());
	},

	/**
	 * Decode response as buffer (non-spec api)
	 *
	 * @return  Promise
	 */
	buffer() {
		return consumeBody.call(this);
	},

	/**
	 * Decode response as text, while automatically detecting the encoding and
	 * trying to decode to UTF-8 (non-spec api)
	 *
	 * @return  Promise
	 */
	textConverted() {
		return consumeBody.call(this).then(buffer => convertBody(buffer, this.headers));
	}
};

// In browsers, all properties are enumerable.
Object.defineProperties(Body.prototype, {
	body: { enumerable: true },
	bodyUsed: { enumerable: true },
	arrayBuffer: { enumerable: true },
	blob: { enumerable: true },
	json: { enumerable: true },
	text: { enumerable: true }
});

Body.mixIn = function (proto) {
	for (const name of Object.getOwnPropertyNames(Body.prototype)) {
		// istanbul ignore else: future proof
		if (!(name in proto)) {
			const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
			Object.defineProperty(proto, name, desc);
		}
	}
};

/**
 * Consume and convert an entire Body to a Buffer.
 *
 * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
 *
 * @return  Promise
 */
function consumeBody() {
	if (this[INTERNALS].disturbed) {
		return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
	}

	this[INTERNALS].disturbed = true;

	if (this[INTERNALS].error) {
		return Body.Promise.reject(this[INTERNALS].error);
	}

	// body is null
	if (this.body === null) {
		return Body.Promise.resolve(Buffer.alloc(0));
	}

	// body is buffer
	if (Buffer.isBuffer(this.body)) {
		return Body.Promise.resolve(this.body);
	}

	// istanbul ignore if: should never happen
	if (!(this.body instanceof Stream)) {
		return Body.Promise.resolve(Buffer.alloc(0));
	}

	// body is stream
	// get ready to actually consume the body
	let accum = [];
	let accumBytes = 0;
	let abort = false;

	return new Body.Promise((resolve, reject) => {
		let resTimeout;

		// allow timeout on slow response body
		if (this.timeout) {
			resTimeout = setTimeout(() => {
				abort = true;
				reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout'));
			}, this.timeout);
		}

		// handle stream errors
		this.body.on('error', err => {
			if (err.name === 'AbortError') {
				// if the request was aborted, reject with this Error
				abort = true;
				reject(err);
			} else {
				// other errors, such as incorrect content-encoding
				reject(new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err));
			}
		});

		this.body.on('data', chunk => {
			if (abort || chunk === null) {
				return;
			}

			if (this.size && accumBytes + chunk.length > this.size) {
				abort = true;
				reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size'));
				return;
			}

			accumBytes += chunk.length;
			accum.push(chunk);
		});

		this.body.on('end', () => {
			if (abort) {
				return;
			}

			clearTimeout(resTimeout);

			try {
				resolve(Buffer.concat(accum, accumBytes));
			} catch (err) {
				// handle streams that have accumulated too much data (issue #414)
				reject(new FetchError(`Could not create Buffer from response body for ${this.url}: ${err.message}`, 'system', err));
			}
		});
	});
}

/**
 * Detect buffer encoding and convert to target encoding
 * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
 *
 * @param   Buffer  buffer    Incoming buffer
 * @param   String  encoding  Target encoding
 * @return  String
 */
function convertBody(buffer, headers) {
	if (typeof convert !== 'function') {
		throw new Error('The package `encoding` must be installed to use the textConverted() function');
	}

	const ct = headers.get('content-type');
	let charset = 'utf-8';
	let res, str;

	// header
	if (ct) {
		res = /charset=([^;]*)/i.exec(ct);
	}

	// no charset in content type, peek at response body for at most 1024 bytes
	str = buffer.slice(0, 1024).toString();

	// html5
	if (!res && str) {
		res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
	}

	// html4
	if (!res && str) {
		res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);

		if (res) {
			res = /charset=(.*)/i.exec(res.pop());
		}
	}

	// xml
	if (!res && str) {
		res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
	}

	// found charset
	if (res) {
		charset = res.pop();

		// prevent decode issues when sites use incorrect encoding
		// ref: https://hsivonen.fi/encoding-menu/
		if (charset === 'gb2312' || charset === 'gbk') {
			charset = 'gb18030';
		}
	}

	// turn raw buffers into a single utf-8 buffer
	return convert(
		buffer,
		'UTF-8',
		charset
	).toString();
}

/**
 * Detect a URLSearchParams object
 * ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143
 *
 * @param   Object  obj     Object to detect by type or brand
 * @return  String
 */
function isURLSearchParams(obj) {
	// Duck-typing as a necessary condition.
	if (typeof obj !== 'object' ||
		typeof obj.append !== 'function' ||
		typeof obj.delete !== 'function' ||
		typeof obj.get !== 'function' ||
		typeof obj.getAll !== 'function' ||
		typeof obj.has !== 'function' ||
		typeof obj.set !== 'function') {
		return false;
	}

	// Brand-checking and more duck-typing as optional condition.
	return obj.constructor.name === 'URLSearchParams' ||
		Object.prototype.toString.call(obj) === '[object URLSearchParams]' ||
		typeof obj.sort === 'function';
}

/**
 * Clone body given Res/Req instance
 *
 * @param   Mixed  instance  Response or Request instance
 * @return  Mixed
 */
export function clone(instance) {
	let p1, p2;
	let body = instance.body;

	// don't allow cloning a used body
	if (instance.bodyUsed) {
		throw new Error('cannot clone body after it is used');
	}

	// check that body is a stream and not form-data object
	// note: we can't clone the form-data object without having it as a dependency
	if ((body instanceof Stream) && (typeof body.getBoundary !== 'function')) {
		// tee instance body
		p1 = new PassThrough();
		p2 = new PassThrough();
		body.pipe(p1);
		body.pipe(p2);
		// set instance body to teed body and return the other teed body
		instance[INTERNALS].body = p1;
		body = p2;
	}

	return body;
}

/**
 * Performs the operation "extract a `Content-Type` value from |object|" as
 * specified in the specification:
 * https://fetch.spec.whatwg.org/#concept-bodyinit-extract
 *
 * This function assumes that instance.body is present.
 *
 * @param   Mixed  instance  Response or Request instance
 */
export function extractContentType(body) {
	// istanbul ignore if: Currently, because of a guard in Request, body
	// can never be null. Included here for completeness.
	if (body === null) {
		// body is null
		return null;
	} else if (typeof body === 'string') {
		// body is string
		return 'text/plain;charset=UTF-8';
	} else if (isURLSearchParams(body)) {
		// body is a URLSearchParams
		return 'application/x-www-form-urlencoded;charset=UTF-8';
	} else if (body instanceof Blob) {
		// body is blob
		return body.type || null;
	} else if (Buffer.isBuffer(body)) {
		// body is buffer
		return null;
	} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
		// body is ArrayBuffer
		return null;
	} else if (ArrayBuffer.isView(body)) {
		// body is ArrayBufferView
		return null;
	} else if (typeof body.getBoundary === 'function') {
		// detect form data input from form-data module
		return `multipart/form-data;boundary=${body.getBoundary()}`;
	} else if (body instanceof Stream) {
		// body is stream
		// can't really do much about this
		return null;
	} else {
		// Body constructor defaults other things to string
		return 'text/plain;charset=UTF-8';
	}
}

/**
 * The Fetch Standard treats this as if "total bytes" is a property on the body.
 * For us, we have to explicitly get it with a function.
 *
 * ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
 *
 * @param   Body    instance   Instance of Body
 * @return  Number?            Number of bytes, or null if not possible
 */
export function getTotalBytes(instance) {
	const {body} = instance;

	// istanbul ignore if: included for completion
	if (body === null) {
		// body is null
		return 0;
	} else if (Buffer.isBuffer(body)) {
		// body is buffer
		return body.length;
	} else if (body && typeof body.getLengthSync === 'function') {
		// detect form data input from form-data module
		if (body._lengthRetrievers && body._lengthRetrievers.length == 0 || // 1.x
			body.hasKnownLength && body.hasKnownLength()) { // 2.x
			return body.getLengthSync();
		}
		return null;
	} else {
		// body is stream
		// can't really do much about this
		return null;
	}
}

/**
 * Write a Body to a Node.js WritableStream (e.g. http.Request) object.
 *
 * @param   Body    instance   Instance of Body
 * @return  Void
 */
export function writeToStream(dest, instance) {
	const {body} = instance;

	if (body === null) {
		// body is null
		dest.end();
	} else if (Buffer.isBuffer(body)) {
		// body is buffer
		dest.write(body);
		dest.end()
	} else {
		// body is stream
		body.pipe(dest);
	}
}

// expose Promise
Body.Promise = global.Promise;
<commit_msg>Allow third party blob implementation (#629)<commit_after>
/**
 * body.js
 *
 * Body interface provides common methods for Request and Response
 */

import Stream from 'stream';

import Blob, { BUFFER } from './blob.js';
import FetchError from './fetch-error.js';

let convert;
try { convert = require('encoding').convert; } catch(e) {}

const INTERNALS = Symbol('Body internals');

// fix an issue where "PassThrough" isn't a named export for node <10
const PassThrough = Stream.PassThrough;

/**
 * Body mixin
 *
 * Ref: https://fetch.spec.whatwg.org/#body
 *
 * @param   Stream  body  Readable stream
 * @param   Object  opts  Response options
 * @return  Void
 */
export default function Body(body, {
	size = 0,
	timeout = 0
} = {}) {
	if (body == null) {
		// body is undefined or null
		body = null;
	} else if (isURLSearchParams(body)) {
		// body is a URLSearchParams
		body = Buffer.from(body.toString());
	} else if (isBlob(body)) {
		// body is blob
	} else if (Buffer.isBuffer(body)) {
		// body is Buffer
	} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
		// body is ArrayBuffer
		body = Buffer.from(body);
	} else if (ArrayBuffer.isView(body)) {
		// body is ArrayBufferView
		body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
	} else if (body instanceof Stream) {
		// body is stream
	} else {
		// none of the above
		// coerce to string then buffer
		body = Buffer.from(String(body));
	}
	this[INTERNALS] = {
		body,
		disturbed: false,
		error: null
	};
	this.size = size;
	this.timeout = timeout;

	if (body instanceof Stream) {
		body.on('error', err => {
			const error = err.name === 'AbortError'
				? err
				: new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err);
			this[INTERNALS].error = error;
		});
	}
}

Body.prototype = {
	get body() {
		return this[INTERNALS].body;
	},

	get bodyUsed() {
		return this[INTERNALS].disturbed;
	},

	/**
	 * Decode response as ArrayBuffer
	 *
	 * @return  Promise
	 */
	arrayBuffer() {
		return consumeBody.call(this).then(buf => buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
	},

	/**
	 * Return raw response as Blob
	 *
	 * @return Promise
	 */
	blob() {
		let ct = this.headers && this.headers.get('content-type') || '';
		return consumeBody.call(this).then(buf => Object.assign(
			// Prevent copying
			new Blob([], {
				type: ct.toLowerCase()
			}),
			{
				[BUFFER]: buf
			}
		));
	},

	/**
	 * Decode response as json
	 *
	 * @return  Promise
	 */
	json() {
		return consumeBody.call(this).then((buffer) => {
			try {
				return JSON.parse(buffer.toString());
			} catch (err) {
				return Body.Promise.reject(new FetchError(`invalid json response body at ${this.url} reason: ${err.message}`, 'invalid-json'));
			}
		})
	},

	/**
	 * Decode response as text
	 *
	 * @return  Promise
	 */
	text() {
		return consumeBody.call(this).then(buffer => buffer.toString());
	},

	/**
	 * Decode response as buffer (non-spec api)
	 *
	 * @return  Promise
	 */
	buffer() {
		return consumeBody.call(this);
	},

	/**
	 * Decode response as text, while automatically detecting the encoding and
	 * trying to decode to UTF-8 (non-spec api)
	 *
	 * @return  Promise
	 */
	textConverted() {
		return consumeBody.call(this).then(buffer => convertBody(buffer, this.headers));
	}
};

// In browsers, all properties are enumerable.
Object.defineProperties(Body.prototype, {
	body: { enumerable: true },
	bodyUsed: { enumerable: true },
	arrayBuffer: { enumerable: true },
	blob: { enumerable: true },
	json: { enumerable: true },
	text: { enumerable: true }
});

Body.mixIn = function (proto) {
	for (const name of Object.getOwnPropertyNames(Body.prototype)) {
		// istanbul ignore else: future proof
		if (!(name in proto)) {
			const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
			Object.defineProperty(proto, name, desc);
		}
	}
};

/**
 * Consume and convert an entire Body to a Buffer.
 *
 * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
 *
 * @return  Promise
 */
function consumeBody() {
	if (this[INTERNALS].disturbed) {
		return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
	}

	this[INTERNALS].disturbed = true;

	if (this[INTERNALS].error) {
		return Body.Promise.reject(this[INTERNALS].error);
	}

	let body = this.body;

	// body is null
	if (body === null) {
		return Body.Promise.resolve(Buffer.alloc(0));
	}

	// body is blob
	if (isBlob(body)) {
		body = body.stream();
	}

	// body is buffer
	if (Buffer.isBuffer(body)) {
		return Body.Promise.resolve(body);
	}

	// istanbul ignore if: should never happen
	if (!(body instanceof Stream)) {
		return Body.Promise.resolve(Buffer.alloc(0));
	}

	// body is stream
	// get ready to actually consume the body
	let accum = [];
	let accumBytes = 0;
	let abort = false;

	return new Body.Promise((resolve, reject) => {
		let resTimeout;

		// allow timeout on slow response body
		if (this.timeout) {
			resTimeout = setTimeout(() => {
				abort = true;
				reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout'));
			}, this.timeout);
		}

		// handle stream errors
		body.on('error', err => {
			if (err.name === 'AbortError') {
				// if the request was aborted, reject with this Error
				abort = true;
				reject(err);
			} else {
				// other errors, such as incorrect content-encoding
				reject(new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err));
			}
		});

		body.on('data', chunk => {
			if (abort || chunk === null) {
				return;
			}

			if (this.size && accumBytes + chunk.length > this.size) {
				abort = true;
				reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size'));
				return;
			}

			accumBytes += chunk.length;
			accum.push(chunk);
		});

		body.on('end', () => {
			if (abort) {
				return;
			}

			clearTimeout(resTimeout);

			try {
				resolve(Buffer.concat(accum, accumBytes));
			} catch (err) {
				// handle streams that have accumulated too much data (issue #414)
				reject(new FetchError(`Could not create Buffer from response body for ${this.url}: ${err.message}`, 'system', err));
			}
		});
	});
}

/**
 * Detect buffer encoding and convert to target encoding
 * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
 *
 * @param   Buffer  buffer    Incoming buffer
 * @param   String  encoding  Target encoding
 * @return  String
 */
function convertBody(buffer, headers) {
	if (typeof convert !== 'function') {
		throw new Error('The package `encoding` must be installed to use the textConverted() function');
	}

	const ct = headers.get('content-type');
	let charset = 'utf-8';
	let res, str;

	// header
	if (ct) {
		res = /charset=([^;]*)/i.exec(ct);
	}

	// no charset in content type, peek at response body for at most 1024 bytes
	str = buffer.slice(0, 1024).toString();

	// html5
	if (!res && str) {
		res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
	}

	// html4
	if (!res && str) {
		res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);

		if (res) {
			res = /charset=(.*)/i.exec(res.pop());
		}
	}

	// xml
	if (!res && str) {
		res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
	}

	// found charset
	if (res) {
		charset = res.pop();

		// prevent decode issues when sites use incorrect encoding
		// ref: https://hsivonen.fi/encoding-menu/
		if (charset === 'gb2312' || charset === 'gbk') {
			charset = 'gb18030';
		}
	}

	// turn raw buffers into a single utf-8 buffer
	return convert(
		buffer,
		'UTF-8',
		charset
	).toString();
}

/**
 * Detect a URLSearchParams object
 * ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143
 *
 * @param   Object  obj     Object to detect by type or brand
 * @return  String
 */
function isURLSearchParams(obj) {
	// Duck-typing as a necessary condition.
	if (typeof obj !== 'object' ||
		typeof obj.append !== 'function' ||
		typeof obj.delete !== 'function' ||
		typeof obj.get !== 'function' ||
		typeof obj.getAll !== 'function' ||
		typeof obj.has !== 'function' ||
		typeof obj.set !== 'function') {
		return false;
	}

	// Brand-checking and more duck-typing as optional condition.
	return obj.constructor.name === 'URLSearchParams' ||
		Object.prototype.toString.call(obj) === '[object URLSearchParams]' ||
		typeof obj.sort === 'function';
}

/**
 * Check if `obj` is a W3C `Blob` object (which `File` inherits from)
 * @param  {*} obj
 * @return {boolean}
 */
function isBlob(obj) {
		return typeof obj === 'object' &&
				typeof obj.arrayBuffer === 'function' &&
				typeof obj.type === 'string' &&
				typeof obj.stream === 'function' &&
				typeof obj.constructor === 'function' &&
				typeof obj.constructor.name === 'string' &&
				/^(Blob|File)$/.test(obj.constructor.name) &&
				/^(Blob|File)$/.test(obj[Symbol.toStringTag])
}

/**
 * Clone body given Res/Req instance
 *
 * @param   Mixed  instance  Response or Request instance
 * @return  Mixed
 */
export function clone(instance) {
	let p1, p2;
	let body = instance.body;

	// don't allow cloning a used body
	if (instance.bodyUsed) {
		throw new Error('cannot clone body after it is used');
	}

	// check that body is a stream and not form-data object
	// note: we can't clone the form-data object without having it as a dependency
	if ((body instanceof Stream) && (typeof body.getBoundary !== 'function')) {
		// tee instance body
		p1 = new PassThrough();
		p2 = new PassThrough();
		body.pipe(p1);
		body.pipe(p2);
		// set instance body to teed body and return the other teed body
		instance[INTERNALS].body = p1;
		body = p2;
	}

	return body;
}

/**
 * Performs the operation "extract a `Content-Type` value from |object|" as
 * specified in the specification:
 * https://fetch.spec.whatwg.org/#concept-bodyinit-extract
 *
 * This function assumes that instance.body is present.
 *
 * @param   Mixed  instance  Response or Request instance
 */
export function extractContentType(body) {
	// istanbul ignore if: Currently, because of a guard in Request, body
	// can never be null. Included here for completeness.
	if (body === null) {
		// body is null
		return null;
	} else if (typeof body === 'string') {
		// body is string
		return 'text/plain;charset=UTF-8';
	} else if (isURLSearchParams(body)) {
		// body is a URLSearchParams
		return 'application/x-www-form-urlencoded;charset=UTF-8';
	} else if (isBlob(body)) {
		// body is blob
		return body.type || null;
	} else if (Buffer.isBuffer(body)) {
		// body is buffer
		return null;
	} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
		// body is ArrayBuffer
		return null;
	} else if (ArrayBuffer.isView(body)) {
		// body is ArrayBufferView
		return null;
	} else if (typeof body.getBoundary === 'function') {
		// detect form data input from form-data module
		return `multipart/form-data;boundary=${body.getBoundary()}`;
	} else if (body instanceof Stream) {
		// body is stream
		// can't really do much about this
		return null;
	} else {
		// Body constructor defaults other things to string
		return 'text/plain;charset=UTF-8';
	}
}

/**
 * The Fetch Standard treats this as if "total bytes" is a property on the body.
 * For us, we have to explicitly get it with a function.
 *
 * ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
 *
 * @param   Body    instance   Instance of Body
 * @return  Number?            Number of bytes, or null if not possible
 */
export function getTotalBytes(instance) {
	const {body} = instance;

	// istanbul ignore if: included for completion
	if (body === null) {
		// body is null
		return 0;
	} else if (isBlob(body)) {
		return body.size;
	} else if (Buffer.isBuffer(body)) {
		// body is buffer
		return body.length;
	} else if (body && typeof body.getLengthSync === 'function') {
		// detect form data input from form-data module
		if (body._lengthRetrievers && body._lengthRetrievers.length == 0 || // 1.x
			body.hasKnownLength && body.hasKnownLength()) { // 2.x
			return body.getLengthSync();
		}
		return null;
	} else {
		// body is stream
		return instance.size || null;
	}
}

/**
 * Write a Body to a Node.js WritableStream (e.g. http.Request) object.
 *
 * @param   Body    instance   Instance of Body
 * @return  Void
 */
export function writeToStream(dest, instance) {
	const {body} = instance;

	if (body === null) {
		// body is null
		dest.end();
	} else if (isBlob(body)) {
		body.stream().pipe(dest);
	} else if (Buffer.isBuffer(body)) {
		// body is buffer
		dest.write(body);
		dest.end()
	} else {
		// body is stream
		body.pipe(dest);
	}
}

// expose Promise
Body.Promise = global.Promise;
<|endoftext|>


<commit_before>(function() {
	function TibiaMap() {
		this.map = null;
		this.floor = 7;
		this.mapFloors = [];
		this.mapDataStore = [];
		this.waypoints = [];
	}
	var URL_PREFIX = 'https://tibiamaps.github.io/tibia-map-data/mapper/';
	// `KNOWN_TILES` is a placeholder for the whitelist of known tiles:
	// https://tibiamaps.github.io/tibia-map-data/mapper/tiles.json
	var KNOWN_TILES = null;
	var fetchKnownTiles = function() {
		var xhr = new XMLHttpRequest();
		xhr.open('GET', URL_PREFIX + 'tiles.json', true);
		xhr.responseType = 'json';
		xhr.onload = function() {
			if (xhr.status == 200) {
				KNOWN_TILES = new Set(xhr.response);
			}
		};
		xhr.send();
	};
	fetchKnownTiles();
	// https://github.com/tibiamaps/tibia-maps-script/blob/master/src/colors.js
	var MAP_COLORS = {
		0x00: { r: 0, g: 0, b: 0 }, // black (empty)
		0x0C: { r: 0, g: 102, b: 0 }, // dark green (trees)
		0x18: { r: 0, g: 204, b: 0 }, // green (grass)
		0x1E: { r: 0, g: 255, b: 0 }, // light green (old swamp)
		0x33: { r: 51, g: 102, b: 153 }, // light blue (water)
		0x56: { r: 102, g: 102, b: 102 }, // dark gray (stone/mountains)
		0x72: { r: 153, g: 51, b: 0 }, // dark brown (earth/stalagmites)
		0x79: { r: 153, g: 102, b: 51 }, // brown (earth)
		0x81: { r: 153, g: 153, b: 153 }, // gray (floor)
		0x8C: { r: 153, g: 255, b: 102 }, // light green (light spots in grassy areas)
		0xB3: { r: 204, g: 255, b: 255 }, // light blue (ice)
		0xBA: { r: 255, g: 51, b: 0 }, // red (city/walls)
		0xC0: { r: 255, g: 102, b: 0 }, // orange (lava)
		0xCF: { r: 255, g: 204, b: 153 }, // beige (sand)
		0xD2: { r: 255, g: 255, b: 0 }, // yellow (ladders/holes/…)
		0xD7: { r: 255, g: 255, b: 255 } // white (snow / target?)
	};
	var BLANK_COLOR = MAP_COLORS[0x00];
	var EMPTY_MAP_DATA = new Uint8Array(new ArrayBuffer(256 * 256));
	var padNumber = function(number, size) {
		var s = '000' + String(number);
		return s.substr(s.length - size);
	};
	var setUrlPosition = function(coords, forceHash) {
		var url = '#' + coords.x + ',' + coords.y + ',' + coords.floor + ':' + coords.zoom;
		if (
			forceHash ||
			(location.hash && location.hash != url)
		) {
			window.history.pushState(null, null, url);
		}
	};
	var getUrlPosition = function() {
		var position = {
			'x': 32368,
			'y': 32198,
			'floor': 7,
			'zoom': 0
		};
		var parts = window.location.hash.slice(1).split(':');
		if (parts[0]) {
			var tempPos = parts[0].split(',');
			if (tempPos.length == 3) {
				position.x = parseInt(tempPos[0], 10);
				position.y = parseInt(tempPos[1], 10);
				position.floor = parseInt(tempPos[2], 10);
			}
		}
		if (parts[1]) {
			position.zoom = parseInt(parts[1], 10);
		}
		return position;
	};
	var modifyLeaflet = function() {
		L.CRS.CustomZoom = L.extend({}, L.CRS.Simple, {
			'scale': function(zoom) {
				switch (zoom) {
					case 0:
						return 256;
					case 1:
						return 512;
					case 2:
						return 1792;
					case 3:
						return 5120;
					case 4:
						return 10240;
					default:
						return 256;
				}
			},
			'latLngToPoint': function(latlng, zoom) {
				var projectedPoint = this.projection.project(latlng);
				var scale = this.scale(zoom);
				return this.transformation._transform(projectedPoint, scale);
			},
			'pointToLatLng': function(point, zoom) {
				var scale = this.scale(zoom);
				var untransformedPoint = this.transformation.untransform(point, scale);
				return this.projection.unproject(untransformedPoint);
			}
		});
	};
	TibiaMap.prototype._getMapData = function(x, y, z, callback) {
		var mapName = padNumber(x, 3) + padNumber(y, 3) + padNumber(z, 2);
		var dataStore = this.mapDataStore;
		if (dataStore[mapName]) {
			window.requestAnimationFrame(function() {
				callback(dataStore[mapName]);
			});
		} else {
			// Only fetch the map file if it’s in the whitelist, or if the whitelist
			// has not finished loading yet.
			if (!KNOWN_TILES || KNOWN_TILES.has(mapName)) {
				var xhr = new XMLHttpRequest();
				xhr.open('GET', URL_PREFIX + mapName + '.map', true);
				xhr.responseType = 'arraybuffer';
				xhr.onload = function(exception) {
					var mapData;
					if (this.status == 200) {
						mapData = new Uint8Array(this.response);
					} else {
						mapData = EMPTY_MAP_DATA;
					}
					dataStore[mapName] = mapData;
					callback(mapData);
				};
				xhr.send();
			}
		}
	};
	TibiaMap.prototype._createMapImageData = function(imageData, baseX, baseY, baseZ, callback) {
		this._getMapData(baseX, baseY, baseZ, function(mapData) {
			var index = 0;
			for (var x = 0; x < 256; x++) {
				for (var y = 0; y < 256; y++) {
					var data = mapData[index];
					var color = MAP_COLORS[data] || BLANK_COLOR;
					var base = (y * imageData.width + x) * 4;
					imageData.data[base + 0] = color.r;
					imageData.data[base + 1] = color.g;
					imageData.data[base + 2] = color.b;
					imageData.data[base + 3] = 255;
					++index;
				}
			}
			callback(imageData);
		});
	};
	TibiaMap.prototype._createMapFloorLayer = function(floor) {
		var mapLayer = this.mapFloors[floor] = new L.GridLayer();
		var map = this.map;
		var _this = this;
		mapLayer._getTileSize = function() {
			return L.CRS.CustomZoom.scale(map.getZoom())
		};
		mapLayer._setZoomTransform = function(level, center, zoom) {
			var coords = getUrlPosition();
			coords.zoom = zoom;
			setUrlPosition(coords, false);
			var scale = this._map.getZoomScale(zoom, level.zoom);
			var translate = level.origin.multiplyBy(scale).subtract(
				this._map._getNewPixelOrigin(center, zoom)
			).round();
			L.DomUtil.setTransform(level.el, translate, scale);
		};
		mapLayer.createTile = function(coords, done) {
			var tile = document.createElement('canvas');
			tile.width = tile.height = 256;
			var ctx = tile.getContext('2d');
			var data = ctx.createImageData(256, 256);
			_this._createMapImageData(data, coords.x, coords.y, floor, function(image) {
				ctx.putImageData(image, 0, 0);
				ctx.imageSmoothingEnabled = false;
				done(null, tile);
			});
			return tile;
		};
		return mapLayer;
	};
	TibiaMap.prototype._showHoverTile = function() {
		var map = this.map;
		var _this = this;
		map.on('mouseout', function(event) {
			_this.hoverTile.setBounds([
				[0, 0],
				[0, 0]
			]);
		});
		map.on('mousemove', function(event) {
			var pos = map.project(event.latlng, 0);
			var x = Math.floor(pos.x);
			var y = Math.floor(pos.y);
			var bounds = [map.unproject([x, y], 0), map.unproject([x + 1, y + 1], 0)];
			if (!_this.hoverTile) {
				_this.hoverTile = L.rectangle(bounds, {
					'color': '#009eff',
					'weight': 1,
					'clickable': false,
					'pointerEvents': 'none'
				}).addTo(map);
			} else {
				_this.hoverTile.setBounds(bounds);
			}
		});
	};
	TibiaMap.prototype.init = function() {
		var _this = this;
		modifyLeaflet();
		// Taken from https://tibiamaps.github.io/tibia-map-data/bounds.json, which
		// rarely (if ever) changes.
		var bounds = { 'xMin': 124, 'xMax': 131, 'yMin': 121, 'yMax': 128 };
		var xPadding = window.innerWidth / 256 / 2;
		var yPadding = window.innerHeight / 256 / 2;
		var yMin = bounds.yMin - yPadding;
		var xMin = bounds.xMin - xPadding;
		var yMax = bounds.yMax + 1 + yPadding;
		var xMax = bounds.xMax + 1 + xPadding;
		var maxBounds = L.latLngBounds(L.latLng(-yMin, xMin), L.latLng(-yMax, xMax));
		var map = this.map = L.map('map', {
			'fadeAnimation': false,
			'minZoom': 0,
			'maxZoom': 4,
			'maxNativeZoom': 0,
			'zoomAnimationThreshold': 4,
			'fullscreenControl': true,
			'attributionControl': false,
			'keyboardPanOffset': 200,
			'unloadInvisibleTiles': false,
			'updateWhenIdle': true,
			'keyboardPanOffset': 500,
			'crs': L.CRS.CustomZoom,
			'maxBounds': maxBounds
		});
		var baseMaps = {
			'Floor +7': this._createMapFloorLayer(0),
			'Floor +6': this._createMapFloorLayer(1),
			'Floor +5': this._createMapFloorLayer(2),
			'Floor +4': this._createMapFloorLayer(3),
			'Floor +3': this._createMapFloorLayer(4),
			'Floor +2': this._createMapFloorLayer(5),
			'Floor +1': this._createMapFloorLayer(6),
			'Ground floor': this._createMapFloorLayer(7),
			'Floor -1': this._createMapFloorLayer(8),
			'Floor -2': this._createMapFloorLayer(9),
			'Floor -3': this._createMapFloorLayer(10),
			'Floor -4': this._createMapFloorLayer(11),
			'Floor -5': this._createMapFloorLayer(12),
			'Floor -6': this._createMapFloorLayer(13),
			'Floor -7': this._createMapFloorLayer(14),
			'Floor -8': this._createMapFloorLayer(15)
		};
		L.control.layers(baseMaps, {}).addTo(map);
		var current = getUrlPosition();
		_this.floor = current.floor;
		map.setView(map.unproject([current.x, current.y], 0), current.zoom);
		this.mapFloors[current.floor].addTo(map);
		window.addEventListener('popstate', function(event) {
			var current = getUrlPosition();
			if (current.floor !== _this.floor) {
				_this.floor = current.floor;
				this.mapFloors[_this.floor].addTo(map);
			}
			if (current.zoom !== map.getZoom()) {
				map.setZoom(current.zoom);
			}
			map.panTo(map.unproject([current.x, current.y], 0));
		});
		map.on('baselayerchange', function(layer) {
			for (var i = 0; i <= 15; i++) {
				if (_this.mapFloors[i]._leaflet_id == layer._leaflet_id) {
					_this.floor = i;
					break;
				}
			};
		});
		map.on('click', function(event) {
			var coords = L.CRS.CustomZoom.latLngToPoint(event.latlng, 0);
			var zoom = map.getZoom();
			var coordX = Math.floor(Math.abs(coords.x));
			var coordY = Math.floor(Math.abs(coords.y));
			setUrlPosition({
				'x': coordX,
				'y': coordY,
				'floor': _this.floor,
				'zoom': zoom
			}, true);
		});
		L.crosshairs().addTo(map);
		L.control.coordinates({
			'position': 'bottomleft',
			'enableUserInput': false,
			'labelFormatterLat': function(lat) {
				var coordX = Math.floor(Math.abs(lat * 256));
				return 'Y: ' + coordX + ' Z: ' + _this.floor;
			},
			'labelFormatterLng': function(lng) {
				var coordY = Math.floor(Math.abs(lng * 256));
				return 'X: ' + coordY;
			}
		}).addTo(map);
		_this._showHoverTile();
	};

	var map = new TibiaMap();
	map.init();

}());
<commit_msg>Hide the fullscreen control on embed pages<commit_after>(function() {
	function TibiaMap() {
		this.map = null;
		this.floor = 7;
		this.mapFloors = [];
		this.mapDataStore = [];
		this.waypoints = [];
	}
	var URL_PREFIX = 'https://tibiamaps.github.io/tibia-map-data/mapper/';
	// `KNOWN_TILES` is a placeholder for the whitelist of known tiles:
	// https://tibiamaps.github.io/tibia-map-data/mapper/tiles.json
	var KNOWN_TILES = null;
	var fetchKnownTiles = function() {
		var xhr = new XMLHttpRequest();
		xhr.open('GET', URL_PREFIX + 'tiles.json', true);
		xhr.responseType = 'json';
		xhr.onload = function() {
			if (xhr.status == 200) {
				KNOWN_TILES = new Set(xhr.response);
			}
		};
		xhr.send();
	};
	fetchKnownTiles();
	// https://github.com/tibiamaps/tibia-maps-script/blob/master/src/colors.js
	var MAP_COLORS = {
		0x00: { r: 0, g: 0, b: 0 }, // black (empty)
		0x0C: { r: 0, g: 102, b: 0 }, // dark green (trees)
		0x18: { r: 0, g: 204, b: 0 }, // green (grass)
		0x1E: { r: 0, g: 255, b: 0 }, // light green (old swamp)
		0x33: { r: 51, g: 102, b: 153 }, // light blue (water)
		0x56: { r: 102, g: 102, b: 102 }, // dark gray (stone/mountains)
		0x72: { r: 153, g: 51, b: 0 }, // dark brown (earth/stalagmites)
		0x79: { r: 153, g: 102, b: 51 }, // brown (earth)
		0x81: { r: 153, g: 153, b: 153 }, // gray (floor)
		0x8C: { r: 153, g: 255, b: 102 }, // light green (light spots in grassy areas)
		0xB3: { r: 204, g: 255, b: 255 }, // light blue (ice)
		0xBA: { r: 255, g: 51, b: 0 }, // red (city/walls)
		0xC0: { r: 255, g: 102, b: 0 }, // orange (lava)
		0xCF: { r: 255, g: 204, b: 153 }, // beige (sand)
		0xD2: { r: 255, g: 255, b: 0 }, // yellow (ladders/holes/…)
		0xD7: { r: 255, g: 255, b: 255 } // white (snow / target?)
	};
	var BLANK_COLOR = MAP_COLORS[0x00];
	var EMPTY_MAP_DATA = new Uint8Array(new ArrayBuffer(256 * 256));
	var isEmbed = location.pathname.indexOf('/embed') != -1;
	var padNumber = function(number, size) {
		var s = '000' + String(number);
		return s.substr(s.length - size);
	};
	var setUrlPosition = function(coords, forceHash) {
		var url = '#' + coords.x + ',' + coords.y + ',' + coords.floor + ':' + coords.zoom;
		if (
			forceHash ||
			(location.hash && location.hash != url)
		) {
			window.history.pushState(null, null, url);
		}
	};
	var getUrlPosition = function() {
		var position = {
			'x': 32368,
			'y': 32198,
			'floor': 7,
			'zoom': 0
		};
		var parts = window.location.hash.slice(1).split(':');
		if (parts[0]) {
			var tempPos = parts[0].split(',');
			if (tempPos.length == 3) {
				position.x = parseInt(tempPos[0], 10);
				position.y = parseInt(tempPos[1], 10);
				position.floor = parseInt(tempPos[2], 10);
			}
		}
		if (parts[1]) {
			position.zoom = parseInt(parts[1], 10);
		}
		return position;
	};
	var modifyLeaflet = function() {
		L.CRS.CustomZoom = L.extend({}, L.CRS.Simple, {
			'scale': function(zoom) {
				switch (zoom) {
					case 0:
						return 256;
					case 1:
						return 512;
					case 2:
						return 1792;
					case 3:
						return 5120;
					case 4:
						return 10240;
					default:
						return 256;
				}
			},
			'latLngToPoint': function(latlng, zoom) {
				var projectedPoint = this.projection.project(latlng);
				var scale = this.scale(zoom);
				return this.transformation._transform(projectedPoint, scale);
			},
			'pointToLatLng': function(point, zoom) {
				var scale = this.scale(zoom);
				var untransformedPoint = this.transformation.untransform(point, scale);
				return this.projection.unproject(untransformedPoint);
			}
		});
	};
	TibiaMap.prototype._getMapData = function(x, y, z, callback) {
		var mapName = padNumber(x, 3) + padNumber(y, 3) + padNumber(z, 2);
		var dataStore = this.mapDataStore;
		if (dataStore[mapName]) {
			window.requestAnimationFrame(function() {
				callback(dataStore[mapName]);
			});
		} else {
			// Only fetch the map file if it’s in the whitelist, or if the whitelist
			// has not finished loading yet.
			if (!KNOWN_TILES || KNOWN_TILES.has(mapName)) {
				var xhr = new XMLHttpRequest();
				xhr.open('GET', URL_PREFIX + mapName + '.map', true);
				xhr.responseType = 'arraybuffer';
				xhr.onload = function(exception) {
					var mapData;
					if (this.status == 200) {
						mapData = new Uint8Array(this.response);
					} else {
						mapData = EMPTY_MAP_DATA;
					}
					dataStore[mapName] = mapData;
					callback(mapData);
				};
				xhr.send();
			}
		}
	};
	TibiaMap.prototype._createMapImageData = function(imageData, baseX, baseY, baseZ, callback) {
		this._getMapData(baseX, baseY, baseZ, function(mapData) {
			var index = 0;
			for (var x = 0; x < 256; x++) {
				for (var y = 0; y < 256; y++) {
					var data = mapData[index];
					var color = MAP_COLORS[data] || BLANK_COLOR;
					var base = (y * imageData.width + x) * 4;
					imageData.data[base + 0] = color.r;
					imageData.data[base + 1] = color.g;
					imageData.data[base + 2] = color.b;
					imageData.data[base + 3] = 255;
					++index;
				}
			}
			callback(imageData);
		});
	};
	TibiaMap.prototype._createMapFloorLayer = function(floor) {
		var mapLayer = this.mapFloors[floor] = new L.GridLayer();
		var map = this.map;
		var _this = this;
		mapLayer._getTileSize = function() {
			return L.CRS.CustomZoom.scale(map.getZoom())
		};
		mapLayer._setZoomTransform = function(level, center, zoom) {
			var coords = getUrlPosition();
			coords.zoom = zoom;
			setUrlPosition(coords, false);
			var scale = this._map.getZoomScale(zoom, level.zoom);
			var translate = level.origin.multiplyBy(scale).subtract(
				this._map._getNewPixelOrigin(center, zoom)
			).round();
			L.DomUtil.setTransform(level.el, translate, scale);
		};
		mapLayer.createTile = function(coords, done) {
			var tile = document.createElement('canvas');
			tile.width = tile.height = 256;
			var ctx = tile.getContext('2d');
			var data = ctx.createImageData(256, 256);
			_this._createMapImageData(data, coords.x, coords.y, floor, function(image) {
				ctx.putImageData(image, 0, 0);
				ctx.imageSmoothingEnabled = false;
				done(null, tile);
			});
			return tile;
		};
		return mapLayer;
	};
	TibiaMap.prototype._showHoverTile = function() {
		var map = this.map;
		var _this = this;
		map.on('mouseout', function(event) {
			_this.hoverTile.setBounds([
				[0, 0],
				[0, 0]
			]);
		});
		map.on('mousemove', function(event) {
			var pos = map.project(event.latlng, 0);
			var x = Math.floor(pos.x);
			var y = Math.floor(pos.y);
			var bounds = [map.unproject([x, y], 0), map.unproject([x + 1, y + 1], 0)];
			if (!_this.hoverTile) {
				_this.hoverTile = L.rectangle(bounds, {
					'color': '#009eff',
					'weight': 1,
					'clickable': false,
					'pointerEvents': 'none'
				}).addTo(map);
			} else {
				_this.hoverTile.setBounds(bounds);
			}
		});
	};
	TibiaMap.prototype.init = function() {
		var _this = this;
		modifyLeaflet();
		// Taken from https://tibiamaps.github.io/tibia-map-data/bounds.json, which
		// rarely (if ever) changes.
		var bounds = { 'xMin': 124, 'xMax': 131, 'yMin': 121, 'yMax': 128 };
		var xPadding = window.innerWidth / 256 / 2;
		var yPadding = window.innerHeight / 256 / 2;
		var yMin = bounds.yMin - yPadding;
		var xMin = bounds.xMin - xPadding;
		var yMax = bounds.yMax + 1 + yPadding;
		var xMax = bounds.xMax + 1 + xPadding;
		var maxBounds = L.latLngBounds(L.latLng(-yMin, xMin), L.latLng(-yMax, xMax));
		var map = this.map = L.map('map', {
			'fadeAnimation': false,
			'minZoom': 0,
			'maxZoom': 4,
			'maxNativeZoom': 0,
			'zoomAnimationThreshold': 4,
			'fullscreenControl': !isEmbed,
			'attributionControl': false,
			'keyboardPanOffset': 200,
			'unloadInvisibleTiles': false,
			'updateWhenIdle': true,
			'keyboardPanOffset': 500,
			'crs': L.CRS.CustomZoom,
			'maxBounds': maxBounds
		});
		var baseMaps = {
			'Floor +7': this._createMapFloorLayer(0),
			'Floor +6': this._createMapFloorLayer(1),
			'Floor +5': this._createMapFloorLayer(2),
			'Floor +4': this._createMapFloorLayer(3),
			'Floor +3': this._createMapFloorLayer(4),
			'Floor +2': this._createMapFloorLayer(5),
			'Floor +1': this._createMapFloorLayer(6),
			'Ground floor': this._createMapFloorLayer(7),
			'Floor -1': this._createMapFloorLayer(8),
			'Floor -2': this._createMapFloorLayer(9),
			'Floor -3': this._createMapFloorLayer(10),
			'Floor -4': this._createMapFloorLayer(11),
			'Floor -5': this._createMapFloorLayer(12),
			'Floor -6': this._createMapFloorLayer(13),
			'Floor -7': this._createMapFloorLayer(14),
			'Floor -8': this._createMapFloorLayer(15)
		};
		L.control.layers(baseMaps, {}).addTo(map);
		var current = getUrlPosition();
		_this.floor = current.floor;
		map.setView(map.unproject([current.x, current.y], 0), current.zoom);
		this.mapFloors[current.floor].addTo(map);
		window.addEventListener('popstate', function(event) {
			var current = getUrlPosition();
			if (current.floor !== _this.floor) {
				_this.floor = current.floor;
				this.mapFloors[_this.floor].addTo(map);
			}
			if (current.zoom !== map.getZoom()) {
				map.setZoom(current.zoom);
			}
			map.panTo(map.unproject([current.x, current.y], 0));
		});
		map.on('baselayerchange', function(layer) {
			for (var i = 0; i <= 15; i++) {
				if (_this.mapFloors[i]._leaflet_id == layer._leaflet_id) {
					_this.floor = i;
					break;
				}
			};
		});
		map.on('click', function(event) {
			var coords = L.CRS.CustomZoom.latLngToPoint(event.latlng, 0);
			var zoom = map.getZoom();
			var coordX = Math.floor(Math.abs(coords.x));
			var coordY = Math.floor(Math.abs(coords.y));
			setUrlPosition({
				'x': coordX,
				'y': coordY,
				'floor': _this.floor,
				'zoom': zoom
			}, true);
		});
		L.crosshairs().addTo(map);
		L.control.coordinates({
			'position': 'bottomleft',
			'enableUserInput': false,
			'labelFormatterLat': function(lat) {
				var coordX = Math.floor(Math.abs(lat * 256));
				return 'Y: ' + coordX + ' Z: ' + _this.floor;
			},
			'labelFormatterLng': function(lng) {
				var coordY = Math.floor(Math.abs(lng * 256));
				return 'X: ' + coordY;
			}
		}).addTo(map);
		_this._showHoverTile();
	};

	var map = new TibiaMap();
	map.init();

}());
<|endoftext|>


<commit_before>var Promise = require('bluebird');
var Transaction = require('../../transaction');
var Helpers = require('../../helpers');
var Plugin = require('../../plugin');
var MediaPlugin = require('../media-plugin');

function AudiobridgePlugin() {
  AudiobridgePlugin.super_.apply(this, arguments);
}

AudiobridgePlugin.NAME = 'janus.plugin.audiobridge';
Helpers.inherits(AudiobridgePlugin, MediaPlugin);
Plugin.register(AudiobridgePlugin.NAME, AudiobridgePlugin);

/**
 * @param {int} roomId
 * @param {Object} [options]
 * @param {boolean} [options.permanent]
 * @param {string} [options.description]
 * @param {string} [options.secret]
 * @param {string} [options.pin]
 * @param {boolean} [options.is_private]
 * @param {int} [options.sampling]
 * @param {boolean} [options.record]
 * @param {string} [options.record_file]
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.createRoom = function(roomId, options) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    if ('success' == response['janus']) {
      return Promise.resolve(response['plugindata']['data']);
    }
    return Promise.reject(new Error('Failed room create'));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'create',
      room: roomId
    }
  };
  Helpers.extend(message.body, options);

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @param {int} roomId
 * @param {Object} [options]
 * @param {string} [options.secret]
 * @param {boolean} [options.permanent]
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.destroyRoom = function(roomId, options) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    if ('success' == response['janus'] && 'destroyed' == response['plugindata']['data']['audiobridge']) {
      return Promise.resolve();
    }
    var errorMessage = response['plugindata']['data']['error'] || 'Failed room destroy';
    return Promise.reject(new Error(errorMessage));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'destroy',
      room: roomId
    }
  };
  Helpers.extend(message.body, options);

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.listRooms = function() {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    if ('success' == response['janus']) {
      return Promise.resolve(response['plugindata']['data']['list']);
    }
    return Promise.reject(new Error('Failed list rooms'));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'list'
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @param {int} roomId
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.listParticipants = function(roomId) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    if ('success' == response['janus']) {
      return Promise.resolve(response['plugindata']['data']['participants']);
    }
    return Promise.reject(new Error('Failed list participants'));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'listparticipants',
      room: roomId
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @param {int} roomId
 * @param {Object} [options]
 * @param {int} [options.id]
 * @param {string} [options.pin]
 * @param {string} [options.display]
 * @param {boolean} [options.muted]
 * @param {int} [options.quality]
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.joinRoom = function(roomId, options) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    if ('error' == response['janus'] || response['plugindata']['data']['error']) {
      return Promise.reject(new Error('Failed room join'));
    }
    return Promise.resolve();
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'join',
      room: roomId
    }
  };
  Helpers.extend(message.body, options);

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.leaveRoom = function() {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    if ('event' == response['janus'] && response['plugindata']['data']['leaving']) {
      return Promise.resolve();
    }
    return Promise.reject(new Error('Failed leave'));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'leave'
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.startOffer = function() {
  var self = this;
  return Promise
    .try(function() {
      return self.getLocalMedia({audio: true, video: false});
    })
    .then(function(stream) {
      self.createPeerConnection();
      self.addStream(stream);
    })
    .then(function() {
      return self.createOffer();
    })
    .then(function(jsep) {
      return self.sendOffer(jsep);
    });
};

/**
 * @param {RTCSessionDescription} jsep
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.sendOffer = function(jsep) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var jsep = response['jsep'];
    if (jsep) {
      this.setRemoteSDP(jsep);
      return jsep;
    }
    return Promise.reject(new Error('Failed sendOffer'));
  }.bind(this));
  var message = {
    janus: 'message',
    transaction: transactionId,
    jsep: jsep,
    body: {
      request: 'configure', muted: false
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

module.exports = AudiobridgePlugin;
<commit_msg>Fix error response handlers<commit_after>var Promise = require('bluebird');
var Transaction = require('../../transaction');
var Helpers = require('../../helpers');
var Plugin = require('../../plugin');
var MediaPlugin = require('../media-plugin');

function AudiobridgePlugin() {
  AudiobridgePlugin.super_.apply(this, arguments);
}

AudiobridgePlugin.NAME = 'janus.plugin.audiobridge';
Helpers.inherits(AudiobridgePlugin, MediaPlugin);
Plugin.register(AudiobridgePlugin.NAME, AudiobridgePlugin);

/**
 * @param {int} roomId
 * @param {Object} [options]
 * @param {boolean} [options.permanent]
 * @param {string} [options.description]
 * @param {string} [options.secret]
 * @param {string} [options.pin]
 * @param {boolean} [options.is_private]
 * @param {int} [options.sampling]
 * @param {boolean} [options.record]
 * @param {string} [options.record_file]
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.createRoom = function(roomId, options) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var errorMessage = response['plugindata']['data']['error'];
    if (!errorMessage || errorMessage.indexOf('already exists') > 0) {
      return Promise.resolve(response['plugindata']['data']);
    }
    return Promise.reject(new Error(errorMessage));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'create',
      room: roomId
    }
  };
  Helpers.extend(message.body, options);

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @param {int} roomId
 * @param {Object} [options]
 * @param {string} [options.secret]
 * @param {boolean} [options.permanent]
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.destroyRoom = function(roomId, options) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var errorMessage = response['plugindata']['data']['error'];
    if (!errorMessage) {
      return Promise.resolve();
    }
    return Promise.reject(new Error(errorMessage));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'destroy',
      room: roomId
    }
  };
  Helpers.extend(message.body, options);

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.listRooms = function() {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var errorMessage = response['plugindata']['data']['error'];
    if (!errorMessage) {
      return Promise.resolve(response['plugindata']['data']['list']);
    }
    return Promise.reject(new Error(errorMessage));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'list'
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @param {int} roomId
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.listParticipants = function(roomId) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var errorMessage = response['plugindata']['data']['error'];
    if (!errorMessage) {
      return Promise.resolve(response['plugindata']['data']['participants']);
    }
    return Promise.reject(new Error(errorMessage));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'listparticipants',
      room: roomId
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @param {int} roomId
 * @param {Object} [options]
 * @param {int} [options.id]
 * @param {string} [options.pin]
 * @param {string} [options.display]
 * @param {boolean} [options.muted]
 * @param {int} [options.quality]
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.joinRoom = function(roomId, options) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var errorMessage = response['plugindata']['data']['error'];
    if (!errorMessage) {
      return Promise.resolve();
    }
    return Promise.reject(new Error(errorMessage));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'join',
      room: roomId
    }
  };
  Helpers.extend(message.body, options);

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.leaveRoom = function() {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var errorMessage = response['plugindata']['data']['error'];
    if (!errorMessage) {
      return Promise.resolve();
    }
    return Promise.reject(new Error(errorMessage));
  });
  var message = {
    janus: 'message',
    transaction: transactionId,
    body: {
      request: 'leave'
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

/**
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.startOffer = function() {
  var self = this;
  return Promise
    .try(function() {
      return self.getLocalMedia({audio: true, video: false});
    })
    .then(function(stream) {
      self.createPeerConnection();
      self.addStream(stream);
    })
    .then(function() {
      return self.createOffer();
    })
    .then(function(jsep) {
      return self.sendOffer(jsep);
    });
};

/**
 * @param {RTCSessionDescription} jsep
 * @returns {Promise}
 */
AudiobridgePlugin.prototype.sendOffer = function(jsep) {
  var transactionId = Transaction.generateRandomId();
  var transaction = new Transaction(transactionId, function(response) {
    var jsep = response['jsep'];
    if (jsep) {
      this.setRemoteSDP(jsep);
      return jsep;
    }
    return Promise.reject(new Error('Failed sendOffer'));
  }.bind(this));
  var message = {
    janus: 'message',
    transaction: transactionId,
    jsep: jsep,
    body: {
      request: 'configure', muted: false
    }
  };

  this.addTransaction(transaction);
  return this.sendSync(message);
};

module.exports = AudiobridgePlugin;
<|endoftext|>


<commit_before>"use strict";
const fs = require("fs");
const path = require("path");

const Requester = require("./http/Requester");
const Utils = require("./utils/Utils");

const Devices = require("./endpoints/Devices");
const Imports = require("./endpoints/Imports");

const _ID_FILENAME = "/iobeam_device_id";
const UTF8_ENC = "utf8";

function _Client(projectId, projectToken, services, requester,
                 deviceId, p) {
    Utils.assertValidProjectId(projectId);
    Utils.assertValidToken(projectToken);
    Utils.assertValidRequester(Requester);

    /* Private vars */
    const _projectId = projectId;
    const _token = projectToken;
    let _deviceId = deviceId || null;
    const _dataset = {};
    const _batches = [];

    // Use the disk cache if the id is there / it is provided.
    const _path = p ? path.join(p, _ID_FILENAME) : null;
    if (_path !== null) {
        try {
            _deviceId = fs.readFileSync(_path, UTF8_ENC).trim();
        } catch (Exception) {
            // File does not exist or unreadable, ignore.
        }
    }

    const _services = services;
    const _msgQueue = [];
    let _inProgress = false;

    /* Private funcs */
    function __setDeviceId(deviceId) {
        Utils.assertValidDeviceId(deviceId);
        _deviceId = deviceId;
        if (_path !== null) {
            fs.writeFile(_path, deviceId + "\n", UTF8_ENC);
        }
    }

    function __hasService(key) {
        if (!Utils.isSet(_services)) {
            return false;
        } else if (!_services.hasOwnProperty(key)) {
            return false;
        } else if (!Utils.isSet(_services[key])) {
            return false;
        } else {
            return true;
        }
    }

    function __startMsgQueue() {
        if (!_inProgress && _msgQueue.length > 0) {
            _inProgress = true;
            const next = _msgQueue.shift();
            next();
        }
    }

    /* Init code */
    _services.devices.initialize(_token, requester);
    _services.imports.initialize(_token, requester);

    return {

        dataset: function() {
            return _dataset;
        },

        setDeviceId: function(deviceId) {
            __setDeviceId(deviceId);
            return this;
        },

        getDeviceId: function() {
            return _deviceId;
        },

        addDataPoint: function(seriesName, point) {
            Utils.assertValidDataPoint(point);
            if (!_dataset.hasOwnProperty(seriesName)) {
                _dataset[seriesName] = [];
            }
            _dataset[seriesName].push(point);
        },

        addDataBatch: function(dataBatch) {
            _batches.push(dataBatch);
        },

        register: function(deviceId, deviceName, callback, setOnDupe) {
            deviceId = deviceId || null;
            setOnDupe = setOnDupe || false;
            if (!__hasService("devices")) {
                return; // TODO throw exception
            } else if (_deviceId !== null && deviceId === _deviceId) {
                return;
            } else if (_deviceId !== null && deviceId === null) {
                return;
            }

            const cb = function(deviceResp) {
                if (deviceResp.success) {
                    __setDeviceId(deviceResp.device.device_id);
                } else if (setOnDupe && deviceResp.error.code === 150) {
                    __setDeviceId(deviceId);
                    deviceResp.success = true;
                    deviceResp.device = {device_id: deviceId};
                    // TODO what about device name?
                }

                if (Utils.isCallback(callback)) {
                    callback(deviceResp.success, deviceResp.device);
                }

                // Let the message queue progress
                _inProgress = false;
                __startMsgQueue();
            };
            _msgQueue.push(function() {
                _services.devices.register(_projectId, cb, deviceId, deviceName);
            });
            __startMsgQueue();
        },

        send: function(callback) {
            if (!__hasService("imports")) {
                return; // TODO throw exception
            }

            const cb = function(resp) {
                if (Utils.isCallback(callback)) {
                    callback(resp.success);
                    for (let k in _dataset) {
                        if (_dataset.hasOwnProperty(k)) {
                            delete _dataset[k];
                        }
                    }
                }

                _inProgress = false;
                __startMsgQueue();
            };

            _msgQueue.push(function() {
                Utils.assertValidDeviceId(_deviceId);
                _services.imports.import(_projectId, _deviceId, _dataset, cb);
            });
            __startMsgQueue();
        }
    };
}

/**
 * Builder for iobeam client object (interacts with iobeam backend).
 * @constructor
 * @param {int} projectId - The project this client is for
 * @param {string} projectToken - The token to use for this project
 */
function _Builder(projectId, projectToken) {
    Utils.assertValidProjectId(projectId);
    Utils.assertValidToken(projectToken);
    Utils.assertValidRequester(Requester);

    const services = {devices: Devices, imports: Imports};
    let _deviceId = null;

    let _savePath = null;
    let _regArgs = null;
    let _backend = Requester;

    return {
        /**
         * Specify the device id for the client object.
         * @param {string} deviceId - Desired device id (needs to be registered).
         */
        setDeviceId: function(deviceId) {
            if (deviceId !== null && typeof(deviceId) === "string") {
                _deviceId = deviceId;
            }
            return this;
        },

        /**
         * Specify the backend base URL to use for API calls
         * @param {string} url - Base URL of API calls, meaning the final
         * URL will be `<url><endpoint>`, e.g., for endpoint "/ping", it will
         * be `<url>/ping`.
         */
        setBackend: function(url) {
            _backend.initialize(url);
            return this;
        },

        /**
         * Set a disk path for saving this client's device ID.
         * @param {string} path - File system path. If not provided, the
         * current directory will be used.
         */
        saveToDisk: function(path) {
            let p = path || null;
            if (p === null) {
                p = ".";
            }
            if (p !== null && typeof(p) === "string") {
                _savePath = p;
            }
            return this;
        },

        /**
         * Register this device using the client.
         * @param {object} deviceSpec - An object with two fields, `deviceId`
         * and `deviceName`, which are used if to manually specify id or name
         * for this device.
         * @param {function} callback - Callback to run after register call,
         * takes two params:
         *      (1) a boolean 'success' on whether it succeeded; and
         *      (2) if successful, a device object (undefined otherwise).
         * @param {boolean} setOnDupe - Sets client deviceId if register call
         * fails with duplicate error message
         */
        register: function(deviceSpec, callback, setOnDupe) {
            _regArgs = {
                callback: callback,
                setOnDupe: (setOnDupe || false)
            };
            if (deviceSpec) {
                if (deviceSpec.deviceId) {
                    _regArgs.deviceId = deviceSpec.deviceId;
                }
                if (deviceSpec.deviceName) {
                    _regArgs.deviceName = deviceSpec.deviceName;
                }
            }
            return this;
        },

        registerOrSetId: function(deviceId, callback) {
            return this.register({deviceId: deviceId}, callback, true);
        },

        /**
         * Builds the iobeam client specified by this builder.
         */
        build: function() {
            const client = _Client(projectId, projectToken, services, _backend,
                                   _deviceId, _savePath);
            if (_regArgs !== null) {
                client.register(_regArgs.deviceId, _regArgs.deviceName,
                                _regArgs.callback, _regArgs.setOnDupe);
            }
            return client;
        }
    };
}

module.exports = {
    Builder: _Builder,
    Datapoint: require("./resources/Datapoint"),
    DataBatch: require("./resources/DataBatch")
};
<commit_msg>main: implicitly use batch format when sending<commit_after>"use strict";
const fs = require("fs");
const path = require("path");

const Requester = require("./http/Requester");
const Utils = require("./utils/Utils");

const Devices = require("./endpoints/Devices");
const Imports = require("./endpoints/Imports");

const DataBatch = require("./resources/DataBatch");

const _ID_FILENAME = "/iobeam_device_id";
const UTF8_ENC = "utf8";

function _Client(projectId, projectToken, services, requester,
                 deviceId, p) {
    Utils.assertValidProjectId(projectId);
    Utils.assertValidToken(projectToken);
    Utils.assertValidRequester(Requester);

    /* Private vars */
    const _projectId = projectId;
    const _token = projectToken;
    let _deviceId = deviceId || null;
    const _dataset = {};
    const _batches = [];

    // Use the disk cache if the id is there / it is provided.
    const _path = p ? path.join(p, _ID_FILENAME) : null;
    if (_path !== null) {
        try {
            _deviceId = fs.readFileSync(_path, UTF8_ENC).trim();
        } catch (Exception) {
            // File does not exist or unreadable, ignore.
        }
    }

    const _services = services;
    const _msgQueue = [];
    let _inProgress = false;

    /* Private funcs */
    function __setDeviceId(deviceId) {
        Utils.assertValidDeviceId(deviceId);
        _deviceId = deviceId;
        if (_path !== null) {
            fs.writeFile(_path, deviceId + "\n", UTF8_ENC);
        }
    }

    function __hasService(key) {
        if (!Utils.isSet(_services)) {
            return false;
        } else if (!_services.hasOwnProperty(key)) {
            return false;
        } else if (!Utils.isSet(_services[key])) {
            return false;
        } else {
            return true;
        }
    }

    function __startMsgQueue() {
        if (!_inProgress && _msgQueue.length > 0) {
            _inProgress = true;
            const next = _msgQueue.shift();
            next();
        }
    }

    function __convertSeriesToBatch(name, values) {
        const batch = new DataBatch([name]);
        for (let i in values) {
            const pt = values[i];
            const temp = {};
            temp[name] = pt.value;
            batch.add(pt.timestamp, temp);
        }
        return batch;
    }

    /* Init code */
    _services.devices.initialize(_token, requester);
    _services.imports.initialize(_token, requester);

    return {

        dataset: function() {
            return _dataset;
        },

        setDeviceId: function(deviceId) {
            __setDeviceId(deviceId);
            return this;
        },

        getDeviceId: function() {
            return _deviceId;
        },

        addDataPoint: function(seriesName, point) {
            Utils.assertValidDataPoint(point);
            if (!_dataset.hasOwnProperty(seriesName)) {
                _dataset[seriesName] = [];
            }
            _dataset[seriesName].push(point);
        },

        addDataBatch: function(dataBatch) {
            _batches.push(dataBatch);
        },

        register: function(deviceId, deviceName, callback, setOnDupe) {
            deviceId = deviceId || null;
            setOnDupe = setOnDupe || false;
            if (!__hasService("devices")) {
                return; // TODO throw exception
            } else if (_deviceId !== null && deviceId === _deviceId) {
                return;
            } else if (_deviceId !== null && deviceId === null) {
                return;
            }

            const cb = function(deviceResp) {
                if (deviceResp.success) {
                    __setDeviceId(deviceResp.device.device_id);
                } else if (setOnDupe && deviceResp.error.code === 150) {
                    __setDeviceId(deviceId);
                    deviceResp.success = true;
                    deviceResp.device = {device_id: deviceId};
                    // TODO what about device name?
                }

                if (Utils.isCallback(callback)) {
                    callback(deviceResp.success, deviceResp.device);
                }

                // Let the message queue progress
                _inProgress = false;
                __startMsgQueue();
            };
            _msgQueue.push(function() {
                _services.devices.register(_projectId, cb, deviceId, deviceName);
            });
            __startMsgQueue();
        },

        send: function(callback) {
            if (!__hasService("imports")) {
                return; // TODO throw exception
            }

            const cb = function(resp) {
                if (Utils.isCallback(callback)) {
                    callback(resp.success);
                }
                _batches.shift();

                _inProgress = false;
                __startMsgQueue();
            };

            for (let s in _dataset) {
                _batches.push(__convertSeriesToBatch(s, _dataset[s]));
                delete _dataset[s];
            }

            for (let i in _batches) {
                const b = _batches[i];
                _msgQueue.push(function() {
                    Utils.assertValidDeviceId(_deviceId);
                    _services.imports.importBatch(_projectId, _deviceId, b, cb);
                });
            }
            __startMsgQueue();
        }
    };
}

/**
 * Builder for iobeam client object (interacts with iobeam backend).
 * @constructor
 * @param {int} projectId - The project this client is for
 * @param {string} projectToken - The token to use for this project
 */
function _Builder(projectId, projectToken) {
    Utils.assertValidProjectId(projectId);
    Utils.assertValidToken(projectToken);
    Utils.assertValidRequester(Requester);

    const services = {devices: Devices, imports: Imports};
    let _deviceId = null;

    let _savePath = null;
    let _regArgs = null;
    let _backend = Requester;

    return {
        /**
         * Specify the device id for the client object.
         * @param {string} deviceId - Desired device id (needs to be registered).
         */
        setDeviceId: function(deviceId) {
            if (deviceId !== null && typeof(deviceId) === "string") {
                _deviceId = deviceId;
            }
            return this;
        },

        /**
         * Specify the backend base URL to use for API calls
         * @param {string} url - Base URL of API calls, meaning the final
         * URL will be `<url><endpoint>`, e.g., for endpoint "/ping", it will
         * be `<url>/ping`.
         */
        setBackend: function(url) {
            _backend.initialize(url);
            return this;
        },

        /**
         * Set a disk path for saving this client's device ID.
         * @param {string} path - File system path. If not provided, the
         * current directory will be used.
         */
        saveToDisk: function(path) {
            let p = path || null;
            if (p === null) {
                p = ".";
            }
            if (p !== null && typeof(p) === "string") {
                _savePath = p;
            }
            return this;
        },

        /**
         * Register this device using the client.
         * @param {object} deviceSpec - An object with two fields, `deviceId`
         * and `deviceName`, which are used if to manually specify id or name
         * for this device.
         * @param {function} callback - Callback to run after register call,
         * takes two params:
         *      (1) a boolean 'success' on whether it succeeded; and
         *      (2) if successful, a device object (undefined otherwise).
         * @param {boolean} setOnDupe - Sets client deviceId if register call
         * fails with duplicate error message
         */
        register: function(deviceSpec, callback, setOnDupe) {
            _regArgs = {
                callback: callback,
                setOnDupe: (setOnDupe || false)
            };
            if (deviceSpec) {
                if (deviceSpec.deviceId) {
                    _regArgs.deviceId = deviceSpec.deviceId;
                }
                if (deviceSpec.deviceName) {
                    _regArgs.deviceName = deviceSpec.deviceName;
                }
            }
            return this;
        },

        registerOrSetId: function(deviceId, callback) {
            return this.register({deviceId: deviceId}, callback, true);
        },

        /**
         * Builds the iobeam client specified by this builder.
         */
        build: function() {
            const client = _Client(projectId, projectToken, services, _backend,
                                   _deviceId, _savePath);
            if (_regArgs !== null) {
                client.register(_regArgs.deviceId, _regArgs.deviceName,
                                _regArgs.callback, _regArgs.setOnDupe);
            }
            return client;
        }
    };
}

module.exports = {
    Builder: _Builder,
    Datapoint: require("./resources/Datapoint"),
    DataBatch: require("./resources/DataBatch")
};
<|endoftext|>


<commit_before>/**
 * Created by GUY on 2015/9/7.
 * @class BI.DatePicker
 * @extends BI.Widget
 */
BI.DatePicker = BI.inherit(BI.Widget, {
    _defaultConfig: function () {
        var conf = BI.DatePicker.superclass._defaultConfig.apply(this, arguments);

        return BI.extend(conf, {
            baseCls: "bi-date-picker",
            height: 40,
            min: "1900-01-01", // 最小日期
            max: "2099-12-31", // 最大日期
        });
    },

    _init: function () {
        BI.DatePicker.superclass._init.apply(this, arguments);
        var self = this; var o = this.options;
        this._year = BI.getDate().getFullYear();
        this._month = BI.getDate().getMonth() + 1;
        this.left = BI.createWidget({
            type: "bi.icon_button",
            cls: "pre-page-h-font",
            width: 24,
            height: 24,
        });
        this.left.on(BI.IconButton.EVENT_CHANGE, function () {
            if (self._month === 1) {
                self.setValue({
                    year: self.year.getValue() - 1,
                    month: 12,
                });
            } else {
                self.setValue({
                    year: self.year.getValue(),
                    month: self.month.getValue() - 1,
                });
            }
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
            self._checkLeftValid();
            self._checkRightValid();
        });

        this.right = BI.createWidget({
            type: "bi.icon_button",
            cls: "next-page-h-font",
            width: 24,
            height: 24,
        });

        this.right.on(BI.IconButton.EVENT_CHANGE, function () {
            if (self._month === 12) {
                self.setValue({
                    year: self.year.getValue() + 1,
                    month: 1,
                });
            } else {
                self.setValue({
                    year: self.year.getValue(),
                    month: self.month.getValue() + 1,
                });
            }
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
            self._checkLeftValid();
            self._checkRightValid();
        });

        this.year = BI.createWidget({
            type: "bi.year_date_combo",
            behaviors: o.behaviors,
            min: o.min,
            max: o.max,
        });
        this.year.on(BI.YearDateCombo.EVENT_CHANGE, function () {
            self.setValue({
                year: self.year.getValue(),
                month: self._refreshMonth()
            });
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
        });
        this.month = BI.createWidget({
            type: "bi.month_date_combo",
            behaviors: o.behaviors,
            allowMonths: this._getAllowMonths()
        });
        this.month.on(BI.MonthDateCombo.EVENT_CHANGE, function () {
            self.setValue({
                year: self.year.getValue(),
                month: self.month.getValue(),
            });
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
        });

        BI.createWidget({
            type: "bi.htape",
            element: this,
            items: [{
                el: {
                    type: "bi.center_adapt",
                    items: [this.left],
                },
                width: 24,
            }, {
                type: "bi.center_adapt",
                items: [{
                    el: {
                        type: "bi.horizontal",
                        width: 120,
                        rgap: 10,
                        items: [{
                            el: this.year,
                            lgap: 10,
                        }, this.month],
                    },
                }],
            }, {
                el: {
                    type: "bi.center_adapt",
                    items: [this.right],
                },
                width: 24,
            }],
        });
        this.setValue({
            year: this._year,
            month: this._month,
        });
    },

    _refreshMonth: function () {
        var month = this.month.getValue();
        this.month.populate(this._getAllowMonths());
        var allowMonth = this._getAllowMonths();
        if (!BI.contains(allowMonth, month)) {
            month = allowMonth[0];
        }
        return month;
    },

    _getAllowMonths: function () {
        var self = this, o = this.options;
        var minDate = BI.parseDateTime(o.min, "%Y-%X-%d");
        var maxDate = BI.parseDateTime(o.max, "%Y-%X-%d");
        minDate.setDate(1);
        maxDate.setDate(1);
        var calcMin = BI.print(minDate, "%Y-%X-%d");
        var calcMax = BI.print(maxDate, "%Y-%X-%d");

        return BI.filter(BI.range(1, 13), function (idx, v) {
            return !BI.checkDateVoid(self.year.getValue(), v, 1, calcMin, calcMax)[0];
        });
    },

    _checkLeftValid: function () {
        var o = this.options;
        var minDate = BI.parseDateTime(o.min, "%Y-%X-%d");
        var valid = !(this._month <= (minDate.getMonth() + 1) && this._year <= minDate.getFullYear());
        this.left.setEnable(valid);

        return valid;
    },

    _checkRightValid: function () {
        var o = this.options;
        var maxDate = BI.parseDateTime(o.max, "%Y-%X-%d");
        var valid = !(this._month >= (maxDate.getMonth() + 1) && this._year >= maxDate.getFullYear());
        this.right.setEnable(valid);

        return valid;
    },

    setMinDate: function (minDate) {
        this.options.min = minDate;
        this.year.setMinDate(minDate);
    },

    setMaxDate: function (maxDate) {
        this.options.max = maxDate;
        this.year.setMaxDate(maxDate);
    },

    setValue: function (ob) {
        this._year = BI.parseInt(ob.year);
        this._month = BI.parseInt(ob.month);
        this.year.setValue(ob.year);
        this._refreshMonth();
        this.month.setValue(ob.month);
        this._checkLeftValid();
        this._checkRightValid();
    },

    getValue: function () {
        return {
            year: this.year.getValue(),
            month: this.month.getValue(),
        };
    },
});
BI.DatePicker.EVENT_CHANGE = "EVENT_CHANGE";
BI.shortcut("bi.date_picker", BI.DatePicker);
<commit_msg>REPORT-35912 fix: 范围<commit_after>/**
 * Created by GUY on 2015/9/7.
 * @class BI.DatePicker
 * @extends BI.Widget
 */
BI.DatePicker = BI.inherit(BI.Widget, {
    _defaultConfig: function () {
        var conf = BI.DatePicker.superclass._defaultConfig.apply(this, arguments);

        return BI.extend(conf, {
            baseCls: "bi-date-picker",
            height: 40,
            min: "1900-01-01", // 最小日期
            max: "2099-12-31", // 最大日期
        });
    },

    _init: function () {
        BI.DatePicker.superclass._init.apply(this, arguments);
        var self = this; var o = this.options;
        this._year = BI.getDate().getFullYear();
        this._month = BI.getDate().getMonth() + 1;
        this.left = BI.createWidget({
            type: "bi.icon_button",
            cls: "pre-page-h-font",
            width: 24,
            height: 24,
        });
        this.left.on(BI.IconButton.EVENT_CHANGE, function () {
            if (self._month === 1) {
                self.setValue({
                    year: self.year.getValue() - 1,
                    month: 12,
                });
            } else {
                self.setValue({
                    year: self.year.getValue(),
                    month: (self.month.getValue() - 1) || BI.getDate().getMonth(),
                });
            }
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
            self._checkLeftValid();
            self._checkRightValid();
        });

        this.right = BI.createWidget({
            type: "bi.icon_button",
            cls: "next-page-h-font",
            width: 24,
            height: 24,
        });

        this.right.on(BI.IconButton.EVENT_CHANGE, function () {
            if (self._month === 12) {
                self.setValue({
                    year: self.year.getValue() + 1,
                    month: 1,
                });
            } else {
                self.setValue({
                    year: self.year.getValue(),
                    month: (self.month.getValue() + 1) || (BI.getDate().getMonth() + 2),
                });
            }
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
            self._checkLeftValid();
            self._checkRightValid();
        });

        this.year = BI.createWidget({
            type: "bi.year_date_combo",
            behaviors: o.behaviors,
            min: o.min,
            max: o.max,
        });
        this.year.on(BI.YearDateCombo.EVENT_CHANGE, function () {
            self.setValue({
                year: self.year.getValue(),
                month: self._refreshMonth()
            });
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
        });
        this.month = BI.createWidget({
            type: "bi.month_date_combo",
            behaviors: o.behaviors,
            allowMonths: this._getAllowMonths()
        });
        this.month.on(BI.MonthDateCombo.EVENT_CHANGE, function () {
            self.setValue({
                year: self.year.getValue(),
                month: self.month.getValue(),
            });
            self.fireEvent(BI.DatePicker.EVENT_CHANGE);
        });

        BI.createWidget({
            type: "bi.htape",
            element: this,
            items: [{
                el: {
                    type: "bi.center_adapt",
                    items: [this.left],
                },
                width: 24,
            }, {
                type: "bi.center_adapt",
                items: [{
                    el: {
                        type: "bi.horizontal",
                        width: 120,
                        rgap: 10,
                        items: [{
                            el: this.year,
                            lgap: 10,
                        }, this.month],
                    },
                }],
            }, {
                el: {
                    type: "bi.center_adapt",
                    items: [this.right],
                },
                width: 24,
            }],
        });
        this.setValue({
            year: this._year,
            month: this._month,
        });
    },

    _refreshMonth: function () {
        var month = this.month.getValue();
        this.month.populate(this._getAllowMonths());
        var allowMonth = this._getAllowMonths();
        if (!BI.contains(allowMonth, month)) {
            month = allowMonth[0];
        }
        return month;
    },

    _getAllowMonths: function () {
        var self = this, o = this.options;
        var minDate = BI.parseDateTime(o.min, "%Y-%X-%d");
        var maxDate = BI.parseDateTime(o.max, "%Y-%X-%d");
        minDate.setDate(1);
        maxDate.setDate(1);
        var calcMin = BI.print(minDate, "%Y-%X-%d");
        var calcMax = BI.print(maxDate, "%Y-%X-%d");

        return BI.filter(BI.range(1, 13), function (idx, v) {
            return !BI.checkDateVoid(self.year.getValue(), v, 1, calcMin, calcMax)[0];
        });
    },

    _checkLeftValid: function () {
        var o = this.options;
        var minDate = BI.parseDateTime(o.min, "%Y-%X-%d");
        var valid = !(this._month <= (minDate.getMonth() + 1) && this._year <= minDate.getFullYear());
        this.left.setEnable(valid);

        return valid;
    },

    _checkRightValid: function () {
        var o = this.options;
        var maxDate = BI.parseDateTime(o.max, "%Y-%X-%d");
        var valid = !(this._month >= (maxDate.getMonth() + 1) && this._year >= maxDate.getFullYear());
        this.right.setEnable(valid);

        return valid;
    },

    setMinDate: function (minDate) {
        this.options.min = minDate;
        this.year.setMinDate(minDate);
    },

    setMaxDate: function (maxDate) {
        this.options.max = maxDate;
        this.year.setMaxDate(maxDate);
    },

    setValue: function (ob) {
        this._year = BI.parseInt(ob.year);
        this._month = BI.parseInt(ob.month);
        this.year.setValue(ob.year);
        this._refreshMonth();
        this.month.setValue(ob.month);
        this._checkLeftValid();
        this._checkRightValid();
    },

    getValue: function () {
        return {
            year: this.year.getValue(),
            month: this.month.getValue(),
        };
    },
});
BI.DatePicker.EVENT_CHANGE = "EVENT_CHANGE";
BI.shortcut("bi.date_picker", BI.DatePicker);
<|endoftext|>