<commit_before>/**
 * DashboardIdeasWidget component
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import Tab from '@material/react-tab';
import TabBar from '@material/react-tab-bar';
import { useHash, useMount } from 'react-use';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { useState, useRef, useCallback } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME } from '../../../datastore/constants';
import whenActive from '../../../../../util/when-active';
import NewIdeas from './NewIdeas';
import SavedIdeas from './SavedIdeas';
import DraftIdeas from './DraftIdeas';
const { useSelect } = Data;

const getHash = ( hash ) => hash ? hash.replace( '#', '' ) : false;
const isValidHash = ( hash ) => getHash( hash ) in DashboardIdeasWidget.tabToIndex;
const getIdeaHubContainerOffset = ( ideaHubWidgetOffsetTop ) => {
	const siteHeaderHeight = document.querySelector( '.googlesitekit-header' )?.offsetHeight || 0;
	const adminBarHeight = document.getElementById( 'wpadminbar' )?.offsetHeight || 0;
	const marginBottom = 24;
	const headerOffset = ( siteHeaderHeight + adminBarHeight + marginBottom ) * -1;
	return ideaHubWidgetOffsetTop + global.window.pageYOffset + headerOffset;
};

const DashboardIdeasWidget = ( { defaultActiveTabIndex, Widget, WidgetReportError } ) => {
	const ideaHubContainer = useRef();
	// ADD getNewIdeas?
	const newIdeas = useSelect( ( select ) => select( STORE_NAME ).getNewIdeas() );

	// THIS
	const savedIdeas = useSelect( ( select ) => select( STORE_NAME ).getSavedIdeas() );
	// THIS
	const draftIdeas = useSelect( ( select ) => select( STORE_NAME ).getDraftPostIdeas() );

	const [ hash, setHash ] = useHash();
	const [ activeTabIndex, setActiveTabIndex ] = useState( DashboardIdeasWidget.tabToIndex[ getHash( hash ) ] || defaultActiveTabIndex );
	const activeTab = DashboardIdeasWidget.tabIDsByIndex[ activeTabIndex ];

	useMount( () => {
		if ( ! ideaHubContainer?.current || ! isValidHash( hash ) ) {
			return;
		}

		setTimeout( () => {
			global.window.scrollTo( {
				top: getIdeaHubContainerOffset( ideaHubContainer.current.getBoundingClientRect().top ),
				behavior: 'smooth',
			} );
		}, 1000 );
	} );

	const handleTabUpdate = useCallback( ( tabIndex ) => {
		setActiveTabIndex( tabIndex );
		setHash( DashboardIdeasWidget.tabIDsByIndex[ tabIndex ] );
	}, [ setHash, setActiveTabIndex ] );

	if ( ! ( newIdeas?.length > 0 ) && ! ( savedIdeas?.length > 0 ) && ! ( draftIdeas?.length > 0 ) ) {
		return
Add here
;
	}

	return (
		<Widget noPadding>

						{ __( 'Ideas to write about based on unanswered searches', 'google-site-kit' ) }

					<TabBar
						activeIndex={ activeTabIndex }
						handleActiveIndexUpdate={ handleTabUpdate }
						className="googlesitekit-idea-hub__tabs"
					>
						<Tab
							focusOnActivate={ false }
						>
							{ __( 'New', 'google-site-kit' ) }
						</Tab>
						<Tab
							focusOnActivate={ false }
						>
							{ __( 'Saved', 'google-site-kit' ) }
							{ savedIdeas?.length >= 0 && ({ savedIdeas.length }) }
						</Tab>
						<Tab
							focusOnActivate={ false }
						>
							{ __( 'Drafts', 'google-site-kit' ) }
							{ draftIdeas?.length >= 0 && ({ draftIdeas.length }) }
						</Tab>
					</TabBar>

						<NewIdeas WidgetReportError={ WidgetReportError } active={ activeTab === 'new-ideas' } />

						<SavedIdeas WidgetReportError={ WidgetReportError } active={ activeTab === 'saved-ideas' } />

						<DraftIdeas WidgetReportError={ WidgetReportError } active={ activeTab === 'draft-ideas' } />

		</Widget>
	);
};

DashboardIdeasWidget.tabToIndex = {
	'new-ideas': 0,
	'saved-ideas': 1,
	'draft-ideas': 2,
};

DashboardIdeasWidget.tabIDsByIndex = Object.keys( DashboardIdeasWidget.tabToIndex );

DashboardIdeasWidget.propTypes = {
	Widget: PropTypes.elementType.isRequired,
	defaultActiveTabIndex: PropTypes.number,
};

DashboardIdeasWidget.defaultProps = {
	defaultActiveTabIndex: 0,
};

export default whenActive( { moduleName: 'idea-hub' } )( DashboardIdeasWidget );
<commit_msg>Render same empty state.<commit_after>/**
 * DashboardIdeasWidget component
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import Tab from '@material/react-tab';
import TabBar from '@material/react-tab-bar';
import { useHash, useMount } from 'react-use';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { useState, useRef, useCallback } from '@wordpress/element';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME } from '../../../datastore/constants';
import whenActive from '../../../../../util/when-active';
import EmptyIcon from '../../../../../../svg/idea-hub-empty-new-ideas.svg';
import NewIdeas from './NewIdeas';
import SavedIdeas from './SavedIdeas';
import DraftIdeas from './DraftIdeas';
import Empty from './Empty';
const { useSelect } = Data;

const getHash = ( hash ) => hash ? hash.replace( '#', '' ) : false;
const isValidHash = ( hash ) => getHash( hash ) in DashboardIdeasWidget.tabToIndex;
const getIdeaHubContainerOffset = ( ideaHubWidgetOffsetTop ) => {
	const siteHeaderHeight = document.querySelector( '.googlesitekit-header' )?.offsetHeight || 0;
	const adminBarHeight = document.getElementById( 'wpadminbar' )?.offsetHeight || 0;
	const marginBottom = 24;
	const headerOffset = ( siteHeaderHeight + adminBarHeight + marginBottom ) * -1;
	return ideaHubWidgetOffsetTop + global.window.pageYOffset + headerOffset;
};

const DashboardIdeasWidget = ( { defaultActiveTabIndex, Widget, WidgetReportError } ) => {
	const ideaHubContainer = useRef();
	// ADD getNewIdeas?
	const newIdeas = useSelect( ( select ) => select( STORE_NAME ).getNewIdeas() );

	// THIS
	const savedIdeas = useSelect( ( select ) => select( STORE_NAME ).getSavedIdeas() );
	// THIS
	const draftIdeas = useSelect( ( select ) => select( STORE_NAME ).getDraftPostIdeas() );

	const [ hash, setHash ] = useHash();
	const [ activeTabIndex, setActiveTabIndex ] = useState( DashboardIdeasWidget.tabToIndex[ getHash( hash ) ] || defaultActiveTabIndex );
	const activeTab = DashboardIdeasWidget.tabIDsByIndex[ activeTabIndex ];

	useMount( () => {
		if ( ! ideaHubContainer?.current || ! isValidHash( hash ) ) {
			return;
		}

		setTimeout( () => {
			global.window.scrollTo( {
				top: getIdeaHubContainerOffset( ideaHubContainer.current.getBoundingClientRect().top ),
				behavior: 'smooth',
			} );
		}, 1000 );
	} );

	const handleTabUpdate = useCallback( ( tabIndex ) => {
		setActiveTabIndex( tabIndex );
		setHash( DashboardIdeasWidget.tabIDsByIndex[ tabIndex ] );
	}, [ setHash, setActiveTabIndex ] );

	if ( ! ( newIdeas?.length > 0 ) && ! ( savedIdeas?.length > 0 ) && ! ( draftIdeas?.length > 0 ) ) {
		return (
			<Widget noPadding>

					<Empty
						Icon={ <EmptyIcon /> }
						title={ __( 'Idea Hub is generating ideas', 'google-site-kit' ) }
						subtitle={ __( 'This could take 24 hours.', 'google-site-kit' ) }
					/>

			</Widget>
		);
	}

	return (
		<Widget noPadding>

						{ __( 'Ideas to write about based on unanswered searches', 'google-site-kit' ) }

					<TabBar
						activeIndex={ activeTabIndex }
						handleActiveIndexUpdate={ handleTabUpdate }
						className="googlesitekit-idea-hub__tabs"
					>
						<Tab
							focusOnActivate={ false }
						>
							{ __( 'New', 'google-site-kit' ) }
						</Tab>
						<Tab
							focusOnActivate={ false }
						>
							{ __( 'Saved', 'google-site-kit' ) }
							{ savedIdeas?.length >= 0 && ({ savedIdeas.length }) }
						</Tab>
						<Tab
							focusOnActivate={ false }
						>
							{ __( 'Drafts', 'google-site-kit' ) }
							{ draftIdeas?.length >= 0 && ({ draftIdeas.length }) }
						</Tab>
					</TabBar>

						<NewIdeas WidgetReportError={ WidgetReportError } active={ activeTab === 'new-ideas' } />

						<SavedIdeas WidgetReportError={ WidgetReportError } active={ activeTab === 'saved-ideas' } />

						<DraftIdeas WidgetReportError={ WidgetReportError } active={ activeTab === 'draft-ideas' } />

		</Widget>
	);
};

DashboardIdeasWidget.tabToIndex = {
	'new-ideas': 0,
	'saved-ideas': 1,
	'draft-ideas': 2,
};

DashboardIdeasWidget.tabIDsByIndex = Object.keys( DashboardIdeasWidget.tabToIndex );

DashboardIdeasWidget.propTypes = {
	Widget: PropTypes.elementType.isRequired,
	defaultActiveTabIndex: PropTypes.number,
};

DashboardIdeasWidget.defaultProps = {
	defaultActiveTabIndex: 0,
};

export default whenActive( { moduleName: 'idea-hub' } )( DashboardIdeasWidget );
<|endoftext|>


<commit_before>const Self = imports.misc.extensionUtils.getCurrentExtension();

// network requests
const Soup = imports.gi.Soup;

const RWG_SETTINGS_SCHEMA_UNSPLASH = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.unsplash';
const RWG_SETTINGS_SCHEMA_WALLHAVEN = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.wallhaven';
const RWG_SETTINGS_SCHEMA_REDDIT = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.reddit';
const RWG_SETTINGS_SCHEMA_GENERIC_JSON = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.genericJSON';

const SettingsModule = Self.imports.settings;
const HistoryModule = Self.imports.history;

const LoggerModule = Self.imports.logger;
const JSONPath = Self.imports.jsonpath.jsonpath;

var BaseAdapter = class {

	constructor() {
		this.logger = new LoggerModule.Logger('RWG3', 'BaseAdapter');
	}

	/**
	 * Retrieves a new url for an image and calls the given callback with an HistoryEntry as parameter.
	 * The history element will be null and the error will be set if an error occurred.
	 *
	 * @param callback(historyElement, error)
	 */
	requestRandomImage(callback) {
		this._error("requestRandomImage not implemented", callback);
	}

	fileName(uri) {
		while (this._isURIEncoded(uri)) {
			uri = decodeURIComponent(uri);
		}

		let base = uri.substring(uri.lastIndexOf('/') + 1);
		if (base.indexOf('?') >= 0) {
			base = base.substr(0, base.indexOf('?'));
		}
		return base;
	}

	_isURIEncoded(uri) {
		uri = uri || '';

		try {
			return uri !== decodeURIComponent(uri);
		} catch (err) {
			this.logger.error(err);
			return false;
		}
	}

	_error(err, callback) {
		let error = {"error": err};
		this.logger.error(JSON.stringify(error));

		if (callback) {
			callback(null, error);
		}
	}

};

var UnsplashAdapter = class extends BaseAdapter {

	constructor() {
		super();

		this.sourceName = 'Unsplash';
		this.sourceUrl = 'https://unsplash.com/';

		// query options
		this.options = {
			'username': '',
			'query': '',
			'collections': [],
			'w': 1920,
			'h': 1080,
			'featured': false
		};

		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_UNSPLASH);
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		this._readOptionsFromSettings();
		let optionsString = this._generateOptionsString();
		let clientParam = 'client_id=64daf439e9b579dd566620c0b07022706522d87b255d06dd01d5470b7f193b8d';

		let url = 'https://api.unsplash.com/photos/random?' + optionsString + clientParam;
		url = encodeURI(url);

		let message = Soup.Message.new('GET', url);

		if (message === null) {
			this._error("Could not create request.", callback);
			return;
		}

		let utmParameters = 'utm_source=RandomWallpaperGnome3&utm_medium=referral&utm_campaign=api-credit';

		session.queue_message(message, (session, message) => {
			let downloadMessage = null;
			let authorName, authorUrl, imageLinkUrl;

			try {
				let data = JSON.parse(message.response_body.data);

				authorName = data.user.name;
				authorUrl = encodeURI(data.user.links.html);
				imageLinkUrl = encodeURI(data.links.html);

				let downloadLocation = data.links.download_location + '?' + clientParam;
				downloadMessage = Soup.Message.new('GET', downloadLocation);
			} catch (e) {
				this._error("Unexpected response. (" + e + ")", callback);
				return;
			}

			if (message === null) {
				this._error("Could not create request.", callback);
				return;
			}

			session.queue_message(downloadMessage, (session, message) => {
				try {
					let downloadData = JSON.parse(message.response_body.data);

					if (callback) {
						let historyEntry = new HistoryModule.HistoryEntry(authorName, this.sourceName, encodeURI(downloadData.url));
						historyEntry.source.sourceUrl = encodeURI(this.sourceUrl + '?' + utmParameters);
						historyEntry.source.authorUrl = encodeURI(authorUrl + '?' + utmParameters);
						historyEntry.source.imageLinkUrl = imageLinkUrl + '?' + utmParameters;
						callback(historyEntry);
					}
				} catch (e) {
					this._error("Unexpected response. (" + e + ")", callback);
					return;
				}
			});
		});
	}

	_generateOptionsString() {
		let options = this.options;
		let optionsString = "";

		for (let key in options) {
			if (options.hasOwnProperty(key)) {
				if (options[key]) {
					optionsString += key + "=" + options[key] + "&";
				}
			}
		}

		return optionsString;
	}

	_readOptionsFromSettings() {
		this.options.query = this._settings.get('unsplash-keyword', 'string');

		this.options.username = this._settings.get('unsplash-username', 'string');
		if (this.options.username && this.options.username[0] === '@') {
			this.options.username = this.options.username.substring(1); // remove @ prefix
		}

		this.options.collections = this._settings.get('unsplash-collections', 'string').split(',').map(
			(elem) => {
				return elem.trim();
			});

		this.options.w = this._settings.get('image-width', 'int');
		this.options.h = this._settings.get('image-height', 'int');

		this.options.featured = this._settings.get('featured-only', 'boolean');
	}
};

var WallhavenAdapter = class extends BaseAdapter {

	constructor() {
		super();

		this.options = {
			'q': '',
			'purity': '110', // SFW, sketchy
			'sorting': 'random',
			'categories': '111', // General, Anime, People
			'resolutions': ['1920x1200', '2560x1440']
		};

		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_WALLHAVEN);
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		this._readOptionsFromSettings();
		let optionsString = this._generateOptionsString();
		let url = 'https://wallhaven.cc/search?' + encodeURI(optionsString);

		let message = Soup.Message.new('GET', url);

		session.queue_message(message, (session, message) => {
			let body = message.response_body.data;
			let urlArray = body.match(new RegExp(/https:\/\/wallhaven.cc\/w\/[0-9a-z]+/g));

			if (!urlArray || urlArray.length === 0) {
				this._error("No image found.", callback);
				return;
			}

			// remove dublicates from array
			let uniqueUrlArray = urlArray.filter(function (item, pos) {
				return urlArray.indexOf(item) == pos;
			});

			// get a random entry from the array
			let url = uniqueUrlArray[Math.floor(Math.random() * uniqueUrlArray.length)];

			message = Soup.Message.new('GET', url);

			if (message === null) {
				this._error("Could not create request.", callback);
				return;
			}

			session.queue_message(message, () => {
				try {
					let body = message.response_body.data;
					let imageDownloadUrl = body.match(new RegExp(/"(https:\/\/w.wallhaven.cc\/full\/.*?)"/))[1];

					if (callback) {
						let historyEntry = new HistoryModule.HistoryEntry(null, 'Wallhaven', imageDownloadUrl);
						historyEntry.source.sourceUrl = 'https://wallhaven.cc/';
						historyEntry.source.imageLinkUrl = url;
						callback(historyEntry);
					}
				} catch (e) {
					this._error("Unexpected response. (" + e + ")", callback);
				}
			})

		});
	}

	_generateOptionsString() {
		let options = this.options;
		let optionsString = "";

		for (let key in options) {
			if (options.hasOwnProperty(key)) {
				if (Array.isArray(options[key])) {
					optionsString += key + "=" + options[key].join() + "&";
				} else {
					if (options[key]) {
						optionsString += key + "=" + options[key] + "&";
					}
				}
			}
		}

		return optionsString;
	}

	_readOptionsFromSettings() {
		this.options.q = this._settings.get('wallhaven-keyword', 'string');

		this.options.resolutions = this._settings.get('resolutions', 'string').split(',');
		this.options.resolutions = this.options.resolutions.map((elem) => {
			return elem.trim();
		});

		let categories = [];
		categories.push(+this._settings.get('category-general', 'boolean')); // + is implicit conversion to int
		categories.push(+this._settings.get('category-anime', 'boolean'));
		categories.push(+this._settings.get('category-people', 'boolean'));
		this.options.categories = categories.join('');

		let purity = [];
		purity.push(+this._settings.get('allow-sfw', 'boolean'));
		purity.push(+this._settings.get('allow-sketchy', 'boolean'));
		purity.push(0); // required by wallhaven
		this.options.purity = purity.join('');
	}
};

var RedditAdapter = class extends BaseAdapter {

	constructor() {
		super();

		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_REDDIT);
	}

	_ampDecode(string) {
		return string.replace(/\&/g, '&');
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		const subreddits = this._settings.get('subreddits', 'string').split(',').map(s => s.trim()).join('+');
		const require_sfw = this._settings.get('allow-sfw', 'boolean');
		const url = encodeURI('https://www.reddit.com/r/' + subreddits + '.json');

		let message = Soup.Message.new('GET', url);

		if (message === null) {
			this._error("Could not create request.", callback);
			return;
		}

		session.queue_message(message, (session, message) => {
			try {
				const submissions = JSON.parse(message.response_body.data).data.children.filter(child => {
					if (child.data.post_hint !== 'image') return false;
					if (require_sfw) return child.data.over_18 === false;
					return true;
				});
				if (submissions.length === 0) {
					this._error("No suitable submissions found!", callback);
					return;
				}
				const random = Math.floor(Math.random() * submissions.length);
				const submission = submissions[random].data;
				const imageDownloadUrl = this._ampDecode(submission.preview.images[0].source.url);

				if (callback) {
					let historyEntry = new HistoryModule.HistoryEntry(null, 'Reddit', imageDownloadUrl);
					historyEntry.source.sourceUrl = 'https://www.reddit.com/' + submission.subreddit_name_prefixed;
					historyEntry.source.imageLinkUrl = 'https://www.reddit.com/' + submission.permalink;
					callback(historyEntry);
				}
			} catch (e) {
				this._error("Could not create request. (" + e + ")", callback);
			}
		});
	}

};

var GenericJsonAdapter = class extends BaseAdapter {

	constructor() {
		super();
		this._jsonPathParser = new JSONPath.JSONPathParser();
		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_GENERIC_JSON);
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		let url = this._settings.get("generic-json-request-url", "string");
		url = encodeURI(url);

		let message = Soup.Message.new('GET', url);

		if (message === null) {
			this._error("Could not create request.", callback);
			return;
		}

		session.queue_message(message, (session, message) => {
			try {
				let response = JSON.parse(message.response_body.data);
				let JSONPath = this._settings.get("generic-json-response-path", "string");
				let imageDownloadUrl = this._jsonPathParser.access(response, JSONPath);
				imageDownloadUrl = this._settings.get("generic-json-url-prefix", "string") + imageDownloadUrl;

				if (callback) {
					let historyEntry = new HistoryModule.HistoryEntry(null, 'Generic JSON Source', imageDownloadUrl);
					historyEntry.source.sourceUrl = imageDownloadUrl;
					callback(historyEntry);
				}
			} catch (e) {
				this._error("Unexpected response. (" + e + ")", callback);
			}
		});

	}

};
<commit_msg>use correct symbol to append parameters to unsplash download link (fixes #99)<commit_after>const Self = imports.misc.extensionUtils.getCurrentExtension();

// network requests
const Soup = imports.gi.Soup;

const RWG_SETTINGS_SCHEMA_UNSPLASH = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.unsplash';
const RWG_SETTINGS_SCHEMA_WALLHAVEN = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.wallhaven';
const RWG_SETTINGS_SCHEMA_REDDIT = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.reddit';
const RWG_SETTINGS_SCHEMA_GENERIC_JSON = 'org.gnome.shell.extensions.space.iflow.randomwallpaper.genericJSON';

const SettingsModule = Self.imports.settings;
const HistoryModule = Self.imports.history;

const LoggerModule = Self.imports.logger;
const JSONPath = Self.imports.jsonpath.jsonpath;

var BaseAdapter = class {

	constructor() {
		this.logger = new LoggerModule.Logger('RWG3', 'BaseAdapter');
	}

	/**
	 * Retrieves a new url for an image and calls the given callback with an HistoryEntry as parameter.
	 * The history element will be null and the error will be set if an error occurred.
	 *
	 * @param callback(historyElement, error)
	 */
	requestRandomImage(callback) {
		this._error("requestRandomImage not implemented", callback);
	}

	fileName(uri) {
		while (this._isURIEncoded(uri)) {
			uri = decodeURIComponent(uri);
		}

		let base = uri.substring(uri.lastIndexOf('/') + 1);
		if (base.indexOf('?') >= 0) {
			base = base.substr(0, base.indexOf('?'));
		}
		return base;
	}

	_isURIEncoded(uri) {
		uri = uri || '';

		try {
			return uri !== decodeURIComponent(uri);
		} catch (err) {
			this.logger.error(err);
			return false;
		}
	}

	_error(err, callback) {
		let error = {"error": err};
		this.logger.error(JSON.stringify(error));

		if (callback) {
			callback(null, error);
		}
	}

};

var UnsplashAdapter = class extends BaseAdapter {

	constructor() {
		super();

		this.sourceName = 'Unsplash';
		this.sourceUrl = 'https://unsplash.com/';

		// query options
		this.options = {
			'username': '',
			'query': '',
			'collections': [],
			'w': 1920,
			'h': 1080,
			'featured': false
		};

		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_UNSPLASH);
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		this._readOptionsFromSettings();
		let optionsString = this._generateOptionsString();
		let clientParam = 'client_id=64daf439e9b579dd566620c0b07022706522d87b255d06dd01d5470b7f193b8d';

		let url = 'https://api.unsplash.com/photos/random?' + optionsString + clientParam;
		url = encodeURI(url);

		let message = Soup.Message.new('GET', url);

		if (message === null) {
			this._error("Could not create request.", callback);
			return;
		}

		let utmParameters = 'utm_source=RandomWallpaperGnome3&utm_medium=referral&utm_campaign=api-credit';

		session.queue_message(message, (session, message) => {
			let downloadMessage = null;
			let authorName, authorUrl, imageLinkUrl;

			try {
				let data = JSON.parse(message.response_body.data);

				authorName = data.user.name;
				authorUrl = encodeURI(data.user.links.html);
				imageLinkUrl = encodeURI(data.links.html);

				// check whether the download link contains parameters already
				let junctionSymbol = data.links.download_location.split('?').length > 1 ? '&' : '?';
				let downloadLocation = data.links.download_location + junctionSymbol + clientParam;
				downloadMessage = Soup.Message.new('GET', downloadLocation);
			} catch (e) {
				this._error("Unexpected response. (" + e + ")", callback);
				return;
			}

			if (message === null) {
				this._error("Could not create request.", callback);
				return;
			}

			session.queue_message(downloadMessage, (session, message) => {
				try {
					let downloadData = JSON.parse(message.response_body.data);

					if (callback) {
						let historyEntry = new HistoryModule.HistoryEntry(authorName, this.sourceName, encodeURI(downloadData.url));
						historyEntry.source.sourceUrl = encodeURI(this.sourceUrl + '?' + utmParameters);
						historyEntry.source.authorUrl = encodeURI(authorUrl + '?' + utmParameters);
						historyEntry.source.imageLinkUrl = imageLinkUrl + '?' + utmParameters;
						callback(historyEntry);
					}
				} catch (e) {
					this._error("Unexpected response. (" + e + ")", callback);
					return;
				}
			});
		});
	}

	_generateOptionsString() {
		let options = this.options;
		let optionsString = "";

		for (let key in options) {
			if (options.hasOwnProperty(key)) {
				if (options[key]) {
					optionsString += key + "=" + options[key] + "&";
				}
			}
		}

		return optionsString;
	}

	_readOptionsFromSettings() {
		this.options.query = this._settings.get('unsplash-keyword', 'string');

		this.options.username = this._settings.get('unsplash-username', 'string');
		if (this.options.username && this.options.username[0] === '@') {
			this.options.username = this.options.username.substring(1); // remove @ prefix
		}

		this.options.collections = this._settings.get('unsplash-collections', 'string').split(',').map(
			(elem) => {
				return elem.trim();
			});

		this.options.w = this._settings.get('image-width', 'int');
		this.options.h = this._settings.get('image-height', 'int');

		this.options.featured = this._settings.get('featured-only', 'boolean');
	}
};

var WallhavenAdapter = class extends BaseAdapter {

	constructor() {
		super();

		this.options = {
			'q': '',
			'purity': '110', // SFW, sketchy
			'sorting': 'random',
			'categories': '111', // General, Anime, People
			'resolutions': ['1920x1200', '2560x1440']
		};

		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_WALLHAVEN);
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		this._readOptionsFromSettings();
		let optionsString = this._generateOptionsString();
		let url = 'https://wallhaven.cc/search?' + encodeURI(optionsString);

		let message = Soup.Message.new('GET', url);

		session.queue_message(message, (session, message) => {
			let body = message.response_body.data;
			let urlArray = body.match(new RegExp(/https:\/\/wallhaven.cc\/w\/[0-9a-z]+/g));

			if (!urlArray || urlArray.length === 0) {
				this._error("No image found.", callback);
				return;
			}

			// remove dublicates from array
			let uniqueUrlArray = urlArray.filter(function (item, pos) {
				return urlArray.indexOf(item) == pos;
			});

			// get a random entry from the array
			let url = uniqueUrlArray[Math.floor(Math.random() * uniqueUrlArray.length)];

			message = Soup.Message.new('GET', url);

			if (message === null) {
				this._error("Could not create request.", callback);
				return;
			}

			session.queue_message(message, () => {
				try {
					let body = message.response_body.data;
					let imageDownloadUrl = body.match(new RegExp(/"(https:\/\/w.wallhaven.cc\/full\/.*?)"/))[1];

					if (callback) {
						let historyEntry = new HistoryModule.HistoryEntry(null, 'Wallhaven', imageDownloadUrl);
						historyEntry.source.sourceUrl = 'https://wallhaven.cc/';
						historyEntry.source.imageLinkUrl = url;
						callback(historyEntry);
					}
				} catch (e) {
					this._error("Unexpected response. (" + e + ")", callback);
				}
			})

		});
	}

	_generateOptionsString() {
		let options = this.options;
		let optionsString = "";

		for (let key in options) {
			if (options.hasOwnProperty(key)) {
				if (Array.isArray(options[key])) {
					optionsString += key + "=" + options[key].join() + "&";
				} else {
					if (options[key]) {
						optionsString += key + "=" + options[key] + "&";
					}
				}
			}
		}

		return optionsString;
	}

	_readOptionsFromSettings() {
		this.options.q = this._settings.get('wallhaven-keyword', 'string');

		this.options.resolutions = this._settings.get('resolutions', 'string').split(',');
		this.options.resolutions = this.options.resolutions.map((elem) => {
			return elem.trim();
		});

		let categories = [];
		categories.push(+this._settings.get('category-general', 'boolean')); // + is implicit conversion to int
		categories.push(+this._settings.get('category-anime', 'boolean'));
		categories.push(+this._settings.get('category-people', 'boolean'));
		this.options.categories = categories.join('');

		let purity = [];
		purity.push(+this._settings.get('allow-sfw', 'boolean'));
		purity.push(+this._settings.get('allow-sketchy', 'boolean'));
		purity.push(0); // required by wallhaven
		this.options.purity = purity.join('');
	}
};

var RedditAdapter = class extends BaseAdapter {

	constructor() {
		super();

		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_REDDIT);
	}

	_ampDecode(string) {
		return string.replace(/\&/g, '&');
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		const subreddits = this._settings.get('subreddits', 'string').split(',').map(s => s.trim()).join('+');
		const require_sfw = this._settings.get('allow-sfw', 'boolean');
		const url = encodeURI('https://www.reddit.com/r/' + subreddits + '.json');

		let message = Soup.Message.new('GET', url);

		if (message === null) {
			this._error("Could not create request.", callback);
			return;
		}

		session.queue_message(message, (session, message) => {
			try {
				const submissions = JSON.parse(message.response_body.data).data.children.filter(child => {
					if (child.data.post_hint !== 'image') return false;
					if (require_sfw) return child.data.over_18 === false;
					return true;
				});
				if (submissions.length === 0) {
					this._error("No suitable submissions found!", callback);
					return;
				}
				const random = Math.floor(Math.random() * submissions.length);
				const submission = submissions[random].data;
				const imageDownloadUrl = this._ampDecode(submission.preview.images[0].source.url);

				if (callback) {
					let historyEntry = new HistoryModule.HistoryEntry(null, 'Reddit', imageDownloadUrl);
					historyEntry.source.sourceUrl = 'https://www.reddit.com/' + submission.subreddit_name_prefixed;
					historyEntry.source.imageLinkUrl = 'https://www.reddit.com/' + submission.permalink;
					callback(historyEntry);
				}
			} catch (e) {
				this._error("Could not create request. (" + e + ")", callback);
			}
		});
	}

};

var GenericJsonAdapter = class extends BaseAdapter {

	constructor() {
		super();
		this._jsonPathParser = new JSONPath.JSONPathParser();
		this._settings = new SettingsModule.Settings(RWG_SETTINGS_SCHEMA_GENERIC_JSON);
	}

	requestRandomImage(callback) {
		let session = new Soup.SessionAsync();

		let url = this._settings.get("generic-json-request-url", "string");
		url = encodeURI(url);

		let message = Soup.Message.new('GET', url);

		if (message === null) {
			this._error("Could not create request.", callback);
			return;
		}

		session.queue_message(message, (session, message) => {
			try {
				let response = JSON.parse(message.response_body.data);
				let JSONPath = this._settings.get("generic-json-response-path", "string");
				let imageDownloadUrl = this._jsonPathParser.access(response, JSONPath);
				imageDownloadUrl = this._settings.get("generic-json-url-prefix", "string") + imageDownloadUrl;

				if (callback) {
					let historyEntry = new HistoryModule.HistoryEntry(null, 'Generic JSON Source', imageDownloadUrl);
					historyEntry.source.sourceUrl = imageDownloadUrl;
					callback(historyEntry);
				}
			} catch (e) {
				this._error("Unexpected response. (" + e + ")", callback);
			}
		});

	}

};
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import shortid from 'shortid';
import { selectResultsTab, assertSQLLabResultsAreEqual } from './sqllab.helper';

export default () => {
  describe('SqlLab query panel', () => {
    beforeEach(() => {
      cy.login();
      cy.server();
      cy.visit('/superset/sqllab');

      cy.route('POST', '/superset/sql_json/').as('sqlLabQuery');
    });

    it('supports entering and running a query', () => {
      // row limit has to be < ~10 for us to be able to determine how many rows
      // are fetched below (because React _Virtualized_ does not render all rows)
      const rowLimit = 3;

      cy.get('#brace-editor textarea')
        .clear({ force: true })
        .type(
        `{selectall}{backspace}SELECT ds, gender, name, num FROM main.birth_names LIMIT ${rowLimit}`,
        { force: true },
      );
      cy.get('#js-sql-toolbar button')
        .eq(0)
        .click();

      cy.wait('@sqlLabQuery');

      selectResultsTab()
        .eq(0) // ensures results tab in case preview tab exists
        .then((tableNodes) => {
          const [header, bodyWrapper] = tableNodes[0].childNodes;
          const body = bodyWrapper.childNodes[0];
          const expectedColCount = header.childNodes.length;
          const expectedRowCount = body.childNodes.length;
          expect(expectedColCount).to.equal(4);
          expect(expectedRowCount).to.equal(rowLimit);
        });
    });

    it.skip('successfully saves a query', () => {
      cy.route('savedqueryviewapi/**').as('getSavedQuery');
      cy.route('superset/tables/**').as('getTables');

      const query = 'SELECT ds, gender, name, num FROM main.birth_names ORDER BY name LIMIT 3';
      const savedQueryTitle = `CYPRESS TEST QUERY ${shortid.generate()}`;

      // we will assert that the results of the query we save, and the saved query are the same
      let initialResultsTable = null;
      let savedQueryResultsTable = null;

      cy.get('#brace-editor textarea')
        .clear({ force: true })
        .type(`{selectall}{backspace}${query}`, { force: true })
        .focus() // focus => blur is required for updating the query that is to be saved
        .blur();

      // ctrl + r also runs query
      cy.get('#brace-editor textarea').type('{ctrl}r', { force: true });

      cy.wait('@sqlLabQuery');

      // Save results to check agains below
      selectResultsTab().then((resultsA) => {
        initialResultsTable = resultsA[0];
      });

      cy.get('#js-sql-toolbar button')
        .eq(1) // save query
        .click();

      // Enter name + save into modal
      cy.get('.modal-sm input')
        .clear({ force: true })
        .type(`{selectall}{backspace}${savedQueryTitle}`, {
        force: true,
      });

      cy.get('.modal-sm .modal-body button')
        .eq(0) // save
        .click();

      // visit saved queries
      cy.visit('/sqllab/my_queries/');

      // first row contains most recent link, follow back to SqlLab
      cy.get('table tr:first-child a[href*="savedQueryId"').click();

      // will timeout without explicitly waiting here
      cy.wait(['@getSavedQuery', '@getTables']);

      // run the saved query
      cy.get('#js-sql-toolbar button')
        .eq(0) // run query
        .click();

      cy.wait('@sqlLabQuery');

      // assert the results of the saved query match the initial results
      selectResultsTab().then((resultsB) => {
        savedQueryResultsTable = resultsB[0];

        assertSQLLabResultsAreEqual(initialResultsTable, savedQueryResultsTable);
      });
    });
  });
};
<commit_msg>[Cypress] Skip another flaky sqllab test (#8374)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import shortid from 'shortid';
import { selectResultsTab, assertSQLLabResultsAreEqual } from './sqllab.helper';

export default () => {
  describe('SqlLab query panel', () => {
    beforeEach(() => {
      cy.login();
      cy.server();
      cy.visit('/superset/sqllab');

      cy.route('POST', '/superset/sql_json/').as('sqlLabQuery');
    });

    it.skip('supports entering and running a query', () => {
      // row limit has to be < ~10 for us to be able to determine how many rows
      // are fetched below (because React _Virtualized_ does not render all rows)
      const rowLimit = 3;

      cy.get('#brace-editor textarea')
        .clear({ force: true })
        .type(
        `{selectall}{backspace}SELECT ds, gender, name, num FROM main.birth_names LIMIT ${rowLimit}`,
        { force: true },
      );
      cy.get('#js-sql-toolbar button')
        .eq(0)
        .click();

      cy.wait('@sqlLabQuery');

      selectResultsTab()
        .eq(0) // ensures results tab in case preview tab exists
        .then((tableNodes) => {
          const [header, bodyWrapper] = tableNodes[0].childNodes;
          const body = bodyWrapper.childNodes[0];
          const expectedColCount = header.childNodes.length;
          const expectedRowCount = body.childNodes.length;
          expect(expectedColCount).to.equal(4);
          expect(expectedRowCount).to.equal(rowLimit);
        });
    });

    it.skip('successfully saves a query', () => {
      cy.route('savedqueryviewapi/**').as('getSavedQuery');
      cy.route('superset/tables/**').as('getTables');

      const query = 'SELECT ds, gender, name, num FROM main.birth_names ORDER BY name LIMIT 3';
      const savedQueryTitle = `CYPRESS TEST QUERY ${shortid.generate()}`;

      // we will assert that the results of the query we save, and the saved query are the same
      let initialResultsTable = null;
      let savedQueryResultsTable = null;

      cy.get('#brace-editor textarea')
        .clear({ force: true })
        .type(`{selectall}{backspace}${query}`, { force: true })
        .focus() // focus => blur is required for updating the query that is to be saved
        .blur();

      // ctrl + r also runs query
      cy.get('#brace-editor textarea').type('{ctrl}r', { force: true });

      cy.wait('@sqlLabQuery');

      // Save results to check agains below
      selectResultsTab().then((resultsA) => {
        initialResultsTable = resultsA[0];
      });

      cy.get('#js-sql-toolbar button')
        .eq(1) // save query
        .click();

      // Enter name + save into modal
      cy.get('.modal-sm input')
        .clear({ force: true })
        .type(`{selectall}{backspace}${savedQueryTitle}`, {
        force: true,
      });

      cy.get('.modal-sm .modal-body button')
        .eq(0) // save
        .click();

      // visit saved queries
      cy.visit('/sqllab/my_queries/');

      // first row contains most recent link, follow back to SqlLab
      cy.get('table tr:first-child a[href*="savedQueryId"').click();

      // will timeout without explicitly waiting here
      cy.wait(['@getSavedQuery', '@getTables']);

      // run the saved query
      cy.get('#js-sql-toolbar button')
        .eq(0) // run query
        .click();

      cy.wait('@sqlLabQuery');

      // assert the results of the saved query match the initial results
      selectResultsTab().then((resultsB) => {
        savedQueryResultsTable = resultsB[0];

        assertSQLLabResultsAreEqual(initialResultsTable, savedQueryResultsTable);
      });
    });
  });
};
<|endoftext|>


<commit_before>/**
 * Created by binarylu on 3/15/16.
 */

var url = "https://chrome-ext.luxiakun.com/leetcode-ext";

$(function() {
    chrome.storage.sync.get({
        progress: 'show'
    }, function(items) {
        if(chrome.runtime.lastError) {
            console.log(chrome.runtime.lastError.message);
        }
        add_chart();
        if (items.progress !== "hide") {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.onreadystatechange = function ()
            {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
                {
                    var jsonData = JSON.parse(xmlhttp.responseText);
                    enhanced_tag(jsonData);
                }
            };
            xmlhttp.open("GET", url, true);
            xmlhttp.send();
        }
    });
});

function enhanced_tag(jsonData) {
    var taglist = {};
    var difficulty_ac = {};
    var difficulty_nac = {};
    $("#problemList").children().last().children().each(function() {
        var problem = $(this).children("td:nth-child(3)").children("a:first-child").html();
        var ac = $(this).children("td:nth-child(1)").children("span:first-child").attr("class");
        if (typeof(jsonData[problem]) == "undefined" || !jsonData[problem]) jsonData[problem] = "unknown";
        for (var j = 0; j < jsonData[problem].length; ++j) {
            var tag = jsonData[problem][j];
            if (typeof(taglist[tag]) == 'undefined' || !taglist[tag]) taglist[tag] = 0;
            taglist[tag] += ac == "ac" ? 1 : 0;
        }

        var difficulty = "";
        var $difficulty_node = $(this).children("td:nth-child(6)");
        if (typeof($difficulty_node.attr("ori_data")) == "undefined") {
            difficulty = $difficulty_node.html();
        } else {
            difficulty = $difficulty_node.attr("ori_data");
        }
        if (typeof(difficulty_ac[difficulty]) == 'undefined' || !difficulty_ac[difficulty]) difficulty_ac[difficulty] = 0;
        if (typeof(difficulty_nac[difficulty]) == 'undefined' || !difficulty_nac[difficulty]) difficulty_nac[difficulty] = 0;
        console.log(difficulty);
        difficulty_ac[difficulty] += ac == "ac" ? 1 : 0;
        difficulty_nac[difficulty] += ac == "ac" ? 0 : 1;
    });

    var Tag = document.getElementsByClassName("sidebar-module")[4].children[0].children;
    for (var i = 1; i < Tag.length; ++i) {
        var total = Tag[i].children[0].innerHTML;
        var tag = Tag[i].children[1].innerHTML;
        var ac = taglist[tag];
        var percent = Math.floor(ac / total * 100);

        var node = document.createElement("div");
        var style = "position:absolute;top:0;left:0;width:" + percent + "%;height:100%;background:green;opacity:0.15;";
        node.setAttribute("style", style);
        Tag[i].childNodes[1].innerHTML = ac + "/" + total;
        Tag[i].appendChild(node);
    }
    console.log(difficulty_ac);
    console.log(difficulty_nac);
    draw_chart(difficulty_ac, difficulty_nac);
}

function add_chart() {
    var $chart = $('
' +
        '
' +
            '
' +
                '' +
                    '' +
                    ' Progress' +
                '' +
            '
' +
            '
' +
                '
abc' +
                '
' +
            '
' +
        '
' +
    '
');
    $(".sidebar-module:last").after($chart);
}

function draw_chart(difficulty_ac, difficulty_nac) {
    //$('#lxk_chart').highcharts({
    var chart = new Highcharts.Chart({
        chart: {
            type: 'column',
            renderTo: 'lxk_chart'
        },
        title: {
            text: 'Your Progress'
        },
        xAxis: {
            categories: ['Easy', 'Medium', 'Hard']
        },
        yAxis: {
            min: 0,
            title: {
                text: ''
            },
            stackLabels: {
                enabled: true,
                style: {
                    fontWeight: 'bold',
                    color: (Highcharts.theme && Highcharts.theme.textColor) || 'gray'
                }
            },
            visible: false
        },
        tooltip: {
            headerFormat: '{point.x}
',
            pointFormat: '{series.name}: {point.y}
Total: {point.stackTotal}'
        },
        plotOptions: {
            column: {
                stacking: 'normal',
                dataLabels: {
                    enabled: true,
                    color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white',
                    style: {
                        textShadow: '0 0 3px black'
                    }
                }
            }
        },
        series: [{
            name: 'Unsolved',
            data: [difficulty_nac.Easy, difficulty_nac.Medium, difficulty_nac.Hard]
        },
        {
            name: 'Solved',
            data: [difficulty_ac.Easy, difficulty_ac.Medium, difficulty_ac.Hard]
        }]
    });
}<commit_msg>modify chart height to 250px<commit_after>/**
 * Created by binarylu on 3/15/16.
 */

var url = "https://chrome-ext.luxiakun.com/leetcode-ext";

$(function() {
    chrome.storage.sync.get({
        progress: 'show'
    }, function(items) {
        if(chrome.runtime.lastError) {
            console.log(chrome.runtime.lastError.message);
        }
        add_chart();
        if (items.progress !== "hide") {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.onreadystatechange = function ()
            {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
                {
                    var jsonData = JSON.parse(xmlhttp.responseText);
                    enhanced_tag(jsonData);
                }
            };
            xmlhttp.open("GET", url, true);
            xmlhttp.send();
        }
    });
});

function enhanced_tag(jsonData) {
    var taglist = {};
    var difficulty_ac = {};
    var difficulty_nac = {};
    $("#problemList").children().last().children().each(function() {
        var problem = $(this).children("td:nth-child(3)").children("a:first-child").html();
        var ac = $(this).children("td:nth-child(1)").children("span:first-child").attr("class");
        if (typeof(jsonData[problem]) == "undefined" || !jsonData[problem]) jsonData[problem] = "unknown";
        for (var j = 0; j < jsonData[problem].length; ++j) {
            var tag = jsonData[problem][j];
            if (typeof(taglist[tag]) == 'undefined' || !taglist[tag]) taglist[tag] = 0;
            taglist[tag] += ac == "ac" ? 1 : 0;
        }

        var difficulty = "";
        var $difficulty_node = $(this).children("td:nth-child(6)");
        if (typeof($difficulty_node.attr("ori_data")) == "undefined") {
            difficulty = $difficulty_node.html();
        } else {
            difficulty = $difficulty_node.attr("ori_data");
        }
        if (typeof(difficulty_ac[difficulty]) == 'undefined' || !difficulty_ac[difficulty]) difficulty_ac[difficulty] = 0;
        if (typeof(difficulty_nac[difficulty]) == 'undefined' || !difficulty_nac[difficulty]) difficulty_nac[difficulty] = 0;
        difficulty_ac[difficulty] += ac == "ac" ? 1 : 0;
        difficulty_nac[difficulty] += ac == "ac" ? 0 : 1;
    });

    var Tag = document.getElementsByClassName("sidebar-module")[4].children[0].children;
    for (var i = 1; i < Tag.length; ++i) {
        var total = Tag[i].children[0].innerHTML;
        var tag = Tag[i].children[1].innerHTML;
        var ac = taglist[tag];
        var percent = Math.floor(ac / total * 100);

        var node = document.createElement("div");
        var style = "position:absolute;top:0;left:0;width:" + percent + "%;height:100%;background:green;opacity:0.15;";
        node.setAttribute("style", style);
        Tag[i].childNodes[1].innerHTML = ac + "/" + total;
        Tag[i].appendChild(node);
    }
    draw_chart(difficulty_ac, difficulty_nac);
}

function add_chart() {
    var $chart = $('
' +
        '
' +
            '
' +
                '' +
                    '' +
                    ' Progress' +
                '' +
            '
' +
            '
' +
                '
' +
                '
' +
            '
' +
        '
' +
    '
');
    $(".sidebar-module:last").after($chart);
}

function draw_chart(difficulty_ac, difficulty_nac) {
    //$('#lxk_chart').highcharts({
    var chart = new Highcharts.Chart({
        chart: {
            type: 'column',
            renderTo: 'lxk_chart',
            height: 250
        },
        title: {
            text: ''
        },
        xAxis: {
            categories: ['Easy', 'Medium', 'Hard']
        },
        yAxis: {
            min: 0,
            title: {
                text: ''
            },
            stackLabels: {
                enabled: true,
                style: {
                    fontWeight: 'bold',
                    color: (Highcharts.theme && Highcharts.theme.textColor) || 'gray'
                }
            },
            visible: false
        },
        tooltip: {
            headerFormat: '{point.x}
',
            pointFormat: '{series.name}: {point.y}
Total: {point.stackTotal}'
        },
        plotOptions: {
            column: {
                stacking: 'normal',
                dataLabels: {
                    enabled: true,
                    color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white',
                    style: {
                        textShadow: '0 0 3px black'
                    }
                }
            }
        },
        series: [{
            name: 'Unsolved',
            data: [difficulty_nac.Easy, difficulty_nac.Medium, difficulty_nac.Hard]
        },
        {
            name: 'Solved',
            data: [difficulty_ac.Easy, difficulty_ac.Medium, difficulty_ac.Hard]
        }]
    });
}<|endoftext|>


<commit_before>module.exports = [
    {
        name: 'Kingsoft Cloud',
        url: 'https://www.ksyun.com/',
        logo: 'http://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E9%87%91%E5%B1%B1%E4%BA%91%20logo&step_word=&hs=0&pn=4&spn=0&di=54040&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&istype=0&ie=utf-8&oe=utf-8&in=&cl=2&lm=-1&st=undefined&cs=3066441701%2C2173877771&os=1294857461%2C2251879882&simid=0%2C0&adpicid=0&lpn=0&ln=698&fr=&fmq=1593590871825_R',
    },
    {
        name: 'Narvar',
        url: 'https://corp.narvar.com/',
        logo: 'https://assets-global.website-files.com/5d278e4646ba9e55d6c67475/5d49ce69c03a64b2d8466816_Logo%20-%20Colored.svg',
    },
    {
        name: 'Giggso',
        url: 'https://giggso.com/',
        logo: 'https://pbs.twimg.com/profile_banners/4179602009/1470596851/600x200'
    },
    {
        name: 'STICorp',
        url: 'https://www.sticorp.com.br/',
        logo: 'https://www.sticorp.com.br/wp-content/uploads/2015/09/logo-horizontal.png',
    },
    {
        name: 'Verizon Media',
        url: 'https://www.verizonmedia.com/',
        logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/09/Verizon_Media_2019_logo.svg/1200px-Verizon_Media_2019_logo.svg.png',
        featured: 1
    },
    {
        name: 'Yahoo! JAPAN',
        url: 'https://www.yahoo.co.jp/',
        logo: 'https://s.yimg.jp/images/top/sp2/cmn/logo-170307.png',
        featured: 2
    },
    {
        name: 'Zhaopin',
        url: 'https://www.zhaopin.com/',
        logo: 'https://fecdn5.zhaopin.cn/www/assets/zhaopin.4659478df1ec9a2e6ed76997a64884f6.png'
    },
    {
        name: 'Onde',
        url: 'https://onde.app/',
        logo: 'https://pbs.twimg.com/profile_images/1142024893229346818/JlCmThRZ_200x200.png',
    },
    {
        name: 'Overstock',
        url: 'https://www.overstock.com/',
        logo: 'https://ak1.ostkcdn.com/img/mxc/OSTK_MAIN_LOGO_20181127KCH.jpg'
    },
    {
        name: 'Ndustrial.io',
        url: 'https://www.ndustrial.io/',
        logo: 'https://images.squarespace-cdn.com/content/v1/56df41f2f699bb94c7a2c6fc/1552657060081-WX86GJXHX7UK81RGE6YU/ke17ZwdGBToddI8pDm48kKCofG1A85AnHnyK5g0bGkJZw-zPPgdn4jUwVcJE1ZvWQUxwkmyExglNqGp0IvTJZUJFbgE-7XRK3dMEBRBhUpzRPjxD400fzchYtVcY_asIzZyz67rHxPkx39rBmDbrxrXjNbFEDrPRRKbjQDhrWWQ/ndustrial.png'
    },
    {
        name: 'Nutanix',
        url: 'https://www.nutanix.com/',
        logo: 'https://www.zuora.com/wp-content/uploads/2017/05/nutanix.png',
        featured: 4
    },
    {
        name: 'The Hut Group',
        url: 'https://www.thg.com',
        logo: 'https://s3-eu-west-1.amazonaws.com/thehutgroup-uploads-test/app/uploads/2016/10/THG.jpg'
    },
    {
        name: 'Turtlequeue',
        url: 'https://turtlequeue.com',
        logo: 'https://turtlequeue.com/logo_black.png'
    },
    {
        name: 'ProxyClick',
        url: 'https://www.proxyclick.com/',
        logo: 'https://www.proxyclick.com/hubfs/assets/images/logo-positive.svg'
    },
    {
        name: 'Toast',
        url: 'https://toasttab.com',
        logo: 'https://cdn2.hubspot.net/hubfs/412971/toast-restaurant-pos.png'
    },
    {
        name: 'Quantel AI',
        url: 'http://quantel.ai/',
        logo: 'https://quantel.ai/wp-content/uploads/2019/09/Quantel-AI-Black-Font-Trans-1024x1024.png'
    },
    {
        name: 'Streamlio',
        url: 'https://streaml.io/',
        logo: 'https://streaml.io/media/logo/Streamlio-Logo.svg'
    },
    {
        name: 'Stream Native',
        url: 'https://streamnative.io/',
        logo: 'https://streamnative.io/static/images/streamnative-logo-white-bakground.png',
    },
    {
        name: 'EMQ',
        url: 'https://www.emqx.io/',
        logo: 'https://www.emqx.io/static/img/logo.svg'
    },
    {
        name: 'Tencent',
        url: 'https://www.tencent.com',
        logo: 'http://pluspng.com/img-png/tencent-png-tencent-logo-logotype-emblem-2-7537.png',
        featured: 3
    },
    {
        name: 'Comcast',
        url: 'https://corporate.comcast.com/',
        logo: 'https://corporate.comcast.com/resources/corporate/assets/images/svg/logo-desktop.svg'
    },
    {
        name: 'China Mobile',
        url: 'https://www.chinamobileltd.com/',
        logo: 'https://www.chinamobileltd.com/en/images/global/logo.svg'
    },
    {
        name: 'VipKid',
        url: 'https://t.vipkid.com.cn/',
        logo: 'https://s.vipkidstatic.com/fe-static/teacher/mkt-pc/img/e74f189.svg'
    },
    {
        name: 'IoTium',
        url: 'https://www.iotium.io/',
        logo: 'https://www.iotium.io/site/wp-content/uploads/2018/04/IoTium-logo.jpg'
    },
    {
        name: 'One Click Retail',
        url: 'https://oneclickretail.com/',
        logo: 'https://www.ascentialedge.com/themes/contrib/custom/images/edge-by-ascential.svg',
        logo_white: true
    },
    {
        name: 'Tuya',
        url: 'https://en.tuya.com/',
        logo: 'https://static1.tuyaus.com/static/portal_intl/img/e25eefa.png'
    },
    {
        name: 'China Telecom',
        url: 'https://www.chinatelecom-h.com/',
        logo: 'https://www.chinatelecom-h.com/en/images/global/logo.png'
    },
    {
        name: 'Kafkaesque',
        url: 'https://kafkaesque.io/',
        logo: 'https://kafkaesque.io/wp-content/uploads/2019/04/color_logo_with_background.svg'
    },
    {
        name: 'OKCoin',
        url: 'https://www.okcoin.com/',
        logo: 'http://img.bafang.com/cdn/assets/imgs/MjAxOTM/784CCB28D9026D43DA6A5F257DA74BF2.png',
        logo_white: true
    },
    {
        name: 'BestPay',
        url: 'https://www.bestpay.com.cn/',
        logo: 'https://www.bestpay.com.cn/global/oig/images/logo.png'
    },
    {
        name: '360',
        url: 'https://www.360.cn/',
        logo: 'http://p2.ssl.qhimg.com/t01d91636862957f76e.png'
    },
    {
        name: 'Lakala',
        url: 'http://www.lakala.com/',
        logo: 'http://www.lakala.com/templets/default/image/temp/logo.png'
    },
    {
        name: 'Huya',
        url: 'https://www.huya.com/',
        logo: 'https://a.msstatic.com/huya/main/img/logo2.png',
        logo_white: true
    },
    {
        name: 'CTWing',
        url: 'https://www.ctwing.cn/',
        logo: 'https://www.ctwing.cn/static/img/logo.168f8b7.svg'
    },
    {
        name: 'Globalegrow E-Commerce',
        url: 'https://www.globalegrow.com/',
        logo: 'https://static.globalegrow.com/dist/images/domeimg/ch/logo.jpg'
    },
    {
        name: 'YunZhiHuLian',
        url: 'https://www.weihudashi.com',
        logo: 'https://www.weihudashi.com/image/header-logo.png'
    },
    {
        name: 'XiaoDuoAI',
        url: 'https://www.xiaoduoai.com/',
        logo: 'https://cdn.xiaoduoai.com/official-site/dist/assets/img/indexPage/xiaoduo_logo_en_white.843927.png',
        logo_white: true
    },
    {
        name: 'Shihong',
        url: 'https://shihong.net.cn',
        logo: 'https://shihong.net.cn/website/static/src/img/shihong1.png'
    },
    {
        name: 'CleverCloud',
        url: 'https://www.clever-cloud.com/en/',
        logo: 'https://www.clever-cloud.com/images/brand-assets/logos/v2/logo_on_white.svg'
    },
    {
        name: 'OVHcloud',
        url: 'https://www.ovhcloud.com/',
        logo: 'https://www.ovh.com/world/images/logo/ovhLogoColor.svg'
    },
    {
        name: 'BrandsEye',
        url: 'https://www.brandseye.com/',
        logo: 'https://www.brandseye.com/theme-images/brandseye-logo-v2.png'
    },
    {
        name: 'Max Kelsen',
        url: 'https://maxkelsen.com/',
        logo: 'https://maxkelsen.com/imgs/logo/Max-Kelsen-logo-black.png'
    },
    {
        name: 'HSL Developer Community of Helsinki Regional Transport Authority',
        url: 'https://dev.hsl.fi/',
        logo: 'https://pbs.twimg.com/profile_images/2507371771/9toyr6r5ngcozfwg42zv_400x400.png'
    },
    // {
    //     name: 'Ottoo',
    //     url: 'https://ottoo.com',
    //     logo: 'https://ottoo.com/assets/images/ottoo-logo.svg'
    // },
    {
        name: 'MercadoLibre',
        url: 'https://www.mercadolibre.com/',
        logo: 'https://d1.awsstatic.com/case-studies/LATAM/mercadolibre.1d9d3766bb06e60948f1c5ce8851ce0eac63b349.png'
    },
    {
        name: 'BIGO',
        url: 'https://www.bigo.sg/',
        logo: 'https://static-web.bigolive.tv/as/bigo-static/official_website/pc/img/about/logo-blue-big.png',
    }
].sort((a, b) => (a.name > b.name) ? 1 : -1);
<commit_msg>Update Kingsoft logo (#7456)<commit_after>module.exports = [
    {
        name: 'Kingsoft Cloud',
        url: 'https://www.ksyun.com/',
        logo: 'https://fe.ksyun.com/header/images/pc/logo.png?a048d39f',
        logo_white: true
    },
    {
        name: 'Narvar',
        url: 'https://corp.narvar.com/',
        logo: 'https://assets-global.website-files.com/5d278e4646ba9e55d6c67475/5d49ce69c03a64b2d8466816_Logo%20-%20Colored.svg',
    },
    {
        name: 'Giggso',
        url: 'https://giggso.com/',
        logo: 'https://pbs.twimg.com/profile_banners/4179602009/1470596851/600x200'
    },
    {
        name: 'STICorp',
        url: 'https://www.sticorp.com.br/',
        logo: 'https://www.sticorp.com.br/wp-content/uploads/2015/09/logo-horizontal.png',
    },
    {
        name: 'Verizon Media',
        url: 'https://www.verizonmedia.com/',
        logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/09/Verizon_Media_2019_logo.svg/1200px-Verizon_Media_2019_logo.svg.png',
        featured: 1
    },
    {
        name: 'Yahoo! JAPAN',
        url: 'https://www.yahoo.co.jp/',
        logo: 'https://s.yimg.jp/images/top/sp2/cmn/logo-170307.png',
        featured: 2
    },
    {
        name: 'Zhaopin',
        url: 'https://www.zhaopin.com/',
        logo: 'https://fecdn5.zhaopin.cn/www/assets/zhaopin.4659478df1ec9a2e6ed76997a64884f6.png'
    },
    {
        name: 'Onde',
        url: 'https://onde.app/',
        logo: 'https://pbs.twimg.com/profile_images/1142024893229346818/JlCmThRZ_200x200.png',
    },
    {
        name: 'Overstock',
        url: 'https://www.overstock.com/',
        logo: 'https://ak1.ostkcdn.com/img/mxc/OSTK_MAIN_LOGO_20181127KCH.jpg'
    },
    {
        name: 'Ndustrial.io',
        url: 'https://www.ndustrial.io/',
        logo: 'https://images.squarespace-cdn.com/content/v1/56df41f2f699bb94c7a2c6fc/1552657060081-WX86GJXHX7UK81RGE6YU/ke17ZwdGBToddI8pDm48kKCofG1A85AnHnyK5g0bGkJZw-zPPgdn4jUwVcJE1ZvWQUxwkmyExglNqGp0IvTJZUJFbgE-7XRK3dMEBRBhUpzRPjxD400fzchYtVcY_asIzZyz67rHxPkx39rBmDbrxrXjNbFEDrPRRKbjQDhrWWQ/ndustrial.png'
    },
    {
        name: 'Nutanix',
        url: 'https://www.nutanix.com/',
        logo: 'https://www.zuora.com/wp-content/uploads/2017/05/nutanix.png',
        featured: 4
    },
    {
        name: 'The Hut Group',
        url: 'https://www.thg.com',
        logo: 'https://s3-eu-west-1.amazonaws.com/thehutgroup-uploads-test/app/uploads/2016/10/THG.jpg'
    },
    {
        name: 'Turtlequeue',
        url: 'https://turtlequeue.com',
        logo: 'https://turtlequeue.com/logo_black.png'
    },
    {
        name: 'ProxyClick',
        url: 'https://www.proxyclick.com/',
        logo: 'https://www.proxyclick.com/hubfs/assets/images/logo-positive.svg'
    },
    {
        name: 'Toast',
        url: 'https://toasttab.com',
        logo: 'https://cdn2.hubspot.net/hubfs/412971/toast-restaurant-pos.png'
    },
    {
        name: 'Quantel AI',
        url: 'http://quantel.ai/',
        logo: 'https://quantel.ai/wp-content/uploads/2019/09/Quantel-AI-Black-Font-Trans-1024x1024.png'
    },
    {
        name: 'Streamlio',
        url: 'https://streaml.io/',
        logo: 'https://streaml.io/media/logo/Streamlio-Logo.svg'
    },
    {
        name: 'Stream Native',
        url: 'https://streamnative.io/',
        logo: 'https://streamnative.io/static/images/streamnative-logo-white-bakground.png',
    },
    {
        name: 'EMQ',
        url: 'https://www.emqx.io/',
        logo: 'https://www.emqx.io/static/img/logo.svg'
    },
    {
        name: 'Tencent',
        url: 'https://www.tencent.com',
        logo: 'http://pluspng.com/img-png/tencent-png-tencent-logo-logotype-emblem-2-7537.png',
        featured: 3
    },
    {
        name: 'Comcast',
        url: 'https://corporate.comcast.com/',
        logo: 'https://corporate.comcast.com/resources/corporate/assets/images/svg/logo-desktop.svg'
    },
    {
        name: 'China Mobile',
        url: 'https://www.chinamobileltd.com/',
        logo: 'https://www.chinamobileltd.com/en/images/global/logo.svg'
    },
    {
        name: 'VipKid',
        url: 'https://t.vipkid.com.cn/',
        logo: 'https://s.vipkidstatic.com/fe-static/teacher/mkt-pc/img/e74f189.svg'
    },
    {
        name: 'IoTium',
        url: 'https://www.iotium.io/',
        logo: 'https://www.iotium.io/site/wp-content/uploads/2018/04/IoTium-logo.jpg'
    },
    {
        name: 'One Click Retail',
        url: 'https://oneclickretail.com/',
        logo: 'https://www.ascentialedge.com/themes/contrib/custom/images/edge-by-ascential.svg',
        logo_white: true
    },
    {
        name: 'Tuya',
        url: 'https://en.tuya.com/',
        logo: 'https://static1.tuyaus.com/static/portal_intl/img/e25eefa.png'
    },
    {
        name: 'China Telecom',
        url: 'https://www.chinatelecom-h.com/',
        logo: 'https://www.chinatelecom-h.com/en/images/global/logo.png'
    },
    {
        name: 'Kafkaesque',
        url: 'https://kafkaesque.io/',
        logo: 'https://kafkaesque.io/wp-content/uploads/2019/04/color_logo_with_background.svg'
    },
    {
        name: 'OKCoin',
        url: 'https://www.okcoin.com/',
        logo: 'http://img.bafang.com/cdn/assets/imgs/MjAxOTM/784CCB28D9026D43DA6A5F257DA74BF2.png',
        logo_white: true
    },
    {
        name: 'BestPay',
        url: 'https://www.bestpay.com.cn/',
        logo: 'https://www.bestpay.com.cn/global/oig/images/logo.png'
    },
    {
        name: '360',
        url: 'https://www.360.cn/',
        logo: 'http://p2.ssl.qhimg.com/t01d91636862957f76e.png'
    },
    {
        name: 'Lakala',
        url: 'http://www.lakala.com/',
        logo: 'http://www.lakala.com/templets/default/image/temp/logo.png'
    },
    {
        name: 'Huya',
        url: 'https://www.huya.com/',
        logo: 'https://a.msstatic.com/huya/main/img/logo2.png',
        logo_white: true
    },
    {
        name: 'CTWing',
        url: 'https://www.ctwing.cn/',
        logo: 'https://www.ctwing.cn/static/img/logo.168f8b7.svg'
    },
    {
        name: 'Globalegrow E-Commerce',
        url: 'https://www.globalegrow.com/',
        logo: 'https://static.globalegrow.com/dist/images/domeimg/ch/logo.jpg'
    },
    {
        name: 'YunZhiHuLian',
        url: 'https://www.weihudashi.com',
        logo: 'https://www.weihudashi.com/image/header-logo.png'
    },
    {
        name: 'XiaoDuoAI',
        url: 'https://www.xiaoduoai.com/',
        logo: 'https://cdn.xiaoduoai.com/official-site/dist/assets/img/indexPage/xiaoduo_logo_en_white.843927.png',
        logo_white: true
    },
    {
        name: 'Shihong',
        url: 'https://shihong.net.cn',
        logo: 'https://shihong.net.cn/website/static/src/img/shihong1.png'
    },
    {
        name: 'CleverCloud',
        url: 'https://www.clever-cloud.com/en/',
        logo: 'https://www.clever-cloud.com/images/brand-assets/logos/v2/logo_on_white.svg'
    },
    {
        name: 'OVHcloud',
        url: 'https://www.ovhcloud.com/',
        logo: 'https://www.ovh.com/world/images/logo/ovhLogoColor.svg'
    },
    {
        name: 'BrandsEye',
        url: 'https://www.brandseye.com/',
        logo: 'https://www.brandseye.com/theme-images/brandseye-logo-v2.png'
    },
    {
        name: 'Max Kelsen',
        url: 'https://maxkelsen.com/',
        logo: 'https://maxkelsen.com/imgs/logo/Max-Kelsen-logo-black.png'
    },
    {
        name: 'HSL Developer Community of Helsinki Regional Transport Authority',
        url: 'https://dev.hsl.fi/',
        logo: 'https://pbs.twimg.com/profile_images/2507371771/9toyr6r5ngcozfwg42zv_400x400.png'
    },
    // {
    //     name: 'Ottoo',
    //     url: 'https://ottoo.com',
    //     logo: 'https://ottoo.com/assets/images/ottoo-logo.svg'
    // },
    {
        name: 'MercadoLibre',
        url: 'https://www.mercadolibre.com/',
        logo: 'https://d1.awsstatic.com/case-studies/LATAM/mercadolibre.1d9d3766bb06e60948f1c5ce8851ce0eac63b349.png'
    },
    {
        name: 'BIGO',
        url: 'https://www.bigo.sg/',
        logo: 'https://static-web.bigolive.tv/as/bigo-static/official_website/pc/img/about/logo-blue-big.png',
    }
].sort((a, b) => (a.name > b.name) ? 1 : -1);
<|endoftext|>


<commit_before>var chrome = require('chrome-framework');
var svgGear = require('./svg-gear');
var svgRaw = require('./svg-raw');
var svgUnfold = require('./svg-unfold');

function renderExtras(pre, options, highlighter) {
  var extras = document.createElement("div");
  extras.className = "extras";

  if (!options.addons.autoHighlight) {
    extras.className += ' auto-highlight-off';
  }

  var optionsLink = document.createElement("a");
  optionsLink.className = "json_viewer icon gear";
  optionsLink.href = chrome.extension.getURL("/pages/options.html");
  optionsLink.target = "_blank";
  optionsLink.title = "Options";
  optionsLink.innerHTML = svgGear;

  var rawLink = document.createElement("a");
  rawLink.className = "json_viewer icon raw";
  rawLink.href = "#";
  rawLink.title = "Original JSON toggle";
  rawLink.innerHTML = svgRaw;
  rawLink.onclick = function(e) {
    e.preventDefault();
    var editor = document.getElementsByClassName('CodeMirror')[0];

    if (pre.hidden) {
      // Raw enabled
      highlighter.hide();
      pre.hidden = false;
      extras.className += ' auto-highlight-off';

    } else {
      // Raw disabled
      highlighter.show();
      pre.hidden = true;
      extras.className = extras.className.replace(/\s+auto-highlight-off/, '');
    }
  }

  var unfoldLink = document.createElement("a");
  unfoldLink.className = "json_viewer icon unfold";
  unfoldLink.href = "#";
  unfoldLink.title = "Fold/Unfold all toogle";
  unfoldLink.innerHTML = svgUnfold;
  unfoldLink.onclick = function(e) {
    e.preventDefault();
    var value = pre.getAttribute('data-folded')

    if (value === 'true' || value === true) {
      highlighter.unfoldAll();
      pre.setAttribute('data-folded', false)

    } else {
      highlighter.fold();
      pre.setAttribute('data-folded', true)
    }
  }

  extras.appendChild(optionsLink);
  extras.appendChild(rawLink);

  // "awaysFold" was a typo but to avoid any problems I'll keep it
  // a while
  pre.setAttribute('data-folded', options.addons.alwaysFold || options.addons.awaysFold)
  extras.appendChild(unfoldLink);

  document.body.appendChild(extras);
}

module.exports = renderExtras;
<commit_msg>Fix tiny typo<commit_after>var chrome = require('chrome-framework');
var svgGear = require('./svg-gear');
var svgRaw = require('./svg-raw');
var svgUnfold = require('./svg-unfold');

function renderExtras(pre, options, highlighter) {
  var extras = document.createElement("div");
  extras.className = "extras";

  if (!options.addons.autoHighlight) {
    extras.className += ' auto-highlight-off';
  }

  var optionsLink = document.createElement("a");
  optionsLink.className = "json_viewer icon gear";
  optionsLink.href = chrome.extension.getURL("/pages/options.html");
  optionsLink.target = "_blank";
  optionsLink.title = "Options";
  optionsLink.innerHTML = svgGear;

  var rawLink = document.createElement("a");
  rawLink.className = "json_viewer icon raw";
  rawLink.href = "#";
  rawLink.title = "Original JSON toggle";
  rawLink.innerHTML = svgRaw;
  rawLink.onclick = function(e) {
    e.preventDefault();
    var editor = document.getElementsByClassName('CodeMirror')[0];

    if (pre.hidden) {
      // Raw enabled
      highlighter.hide();
      pre.hidden = false;
      extras.className += ' auto-highlight-off';

    } else {
      // Raw disabled
      highlighter.show();
      pre.hidden = true;
      extras.className = extras.className.replace(/\s+auto-highlight-off/, '');
    }
  }

  var unfoldLink = document.createElement("a");
  unfoldLink.className = "json_viewer icon unfold";
  unfoldLink.href = "#";
  unfoldLink.title = "Fold/Unfold all toggle";
  unfoldLink.innerHTML = svgUnfold;
  unfoldLink.onclick = function(e) {
    e.preventDefault();
    var value = pre.getAttribute('data-folded')

    if (value === 'true' || value === true) {
      highlighter.unfoldAll();
      pre.setAttribute('data-folded', false)

    } else {
      highlighter.fold();
      pre.setAttribute('data-folded', true)
    }
  }

  extras.appendChild(optionsLink);
  extras.appendChild(rawLink);

  // "awaysFold" was a typo but to avoid any problems I'll keep it
  // a while
  pre.setAttribute('data-folded', options.addons.alwaysFold || options.addons.awaysFold)
  extras.appendChild(unfoldLink);

  document.body.appendChild(extras);
}

module.exports = renderExtras;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { formatNumber, formatPercentage } from 'common/format';
import Toggle from 'react-toggle';
import ReactTooltip from 'react-tooltip';
import PerformanceBar from 'common/performanceBar';
import SpellLink from 'common/SpellLink';

class HealingEfficiencyBreakdown extends React.Component {
  static propTypes = {
    tracker: PropTypes.object.isRequired,
  };

  constructor() {
    super();
    this.state = {
      showHealing: true,
      detailedView: false,
      showCooldowns: false,
    };
  }

  HealingEfficiencyTable = (props) => {
    const { tracker } = props;
    const { spells, topHpm, topDpm, topHpet, topDpet } = tracker.getAllSpellStats(this.state.showCooldowns);

    const spellArray = Object.values(spells);

    spellArray.sort((a, b) => {
      if (this.state.showHealing){
        if (a.hpm  b.hpm) {
          return -1;
        }
      } else {
        if (a.dpm  b.dpm) {
          return -1;
        }
      }

      return 0;
    });

    const spellRows = spellArray.map((spellDetail => {
      if (spellDetail.casts > 0) {
        return this.HealingEfficiencySpellRow(spellDetail, topHpm, topDpm, topHpet, topDpet);
      }
      return null;
    }));

    return spellRows;
  };

  HealingEfficiencySpellRow = (spellDetail, topHpm, topDpm, topHpet, topDpet) => {
    return (

          <SpellLink id={spellDetail.spell.id} />

        {this.state.detailedView ? <this.DetailView spellDetail={spellDetail} /> : <this.BarView spellDetail={spellDetail} topHpm={topHpm} topDpm={topDpm} topHpet={topHpet} topDpet={topDpet} />}

    );
  };

  BarHeader = () => {
    return (
      <>
        Mana Spent
        {this.state.showHealing &&
        <>
          Healing per mana spent
          Healing per second spent casting
        </>
        }
        {!this.state.showHealing &&
        <>
          Damage per mana spent
          Damage per second spent casting
        </>
        }
      </>
    );
  };

  BarView = (props) => {
    const { spellDetail, topHpm, topDpm, topHpet, topDpet } = props;
    const hasHealing = spellDetail.healingDone;
    const hasDamage = spellDetail.damageDone > 0;
    const barWidth = 20;

    return (
      <>

          {formatNumber(spellDetail.manaSpent)}
          {' (' + formatPercentage(spellDetail.manaPercentSpent) + '%)'}

        {this.state.showHealing &&
        <>
          {hasHealing ? formatNumber(spellDetail.hpm) : '-'}
          <PerformanceBar percent={spellDetail.hpm / topHpm} />

          {hasHealing ? formatNumber(spellDetail.hpet * 1000) : '-'}
          <PerformanceBar percent={(spellDetail.hpet / topHpet)} />
        </>
        }
        {!this.state.showHealing &&
        <>
          {hasDamage ? formatNumber(spellDetail.dpm) : '-'}
          <PerformanceBar percent={spellDetail.dpm / topDpm} />

          {hasDamage ? formatNumber(spellDetail.dpet * 1000) : '-'}
          <PerformanceBar percent={(spellDetail.dpet / topDpet)} />
        </>
        }
      </>
    );
  };

  DetailHeader = () => {
    return (
      <>

          <dfn data-tip={`Total Casts (Number of targets hit)`}>Casts</dfn>

        Mana Spent
        {this.state.showHealing &&
        <>
          Healing Done
          Overhealing

            <dfn data-tip={`Healing per mana spent casting the spell`}>HPM</dfn>

            <dfn data-tip={`Healing per second spent casting the spell`}>HPET</dfn>

        </>
        }
        {!this.state.showHealing &&
        <>
          Damage Done

            <dfn data-tip={`Damage per mana spent casting the spell`}>DPM</dfn>

            <dfn data-tip={`Damage per second spent casting the spell`}>DPET</dfn>

        </>
        }
      </>
    );
  };

  DetailView = (props) => {
    const { spellDetail } = props;
    const hasHealing = spellDetail.healingDone;
    const hasOverhealing = spellDetail.healingDone > 0 || spellDetail.overhealingDone > 0;
    const hasDamage = spellDetail.damageDone > 0;

    return (
      <>
        {spellDetail.casts} ({Math.floor(spellDetail.healingHits + spellDetail.damageHits)})

          {formatNumber(spellDetail.manaSpent)}
          {' (' + formatPercentage(spellDetail.manaPercentSpent) + '%)'}

        {this.state.showHealing &&
        <>

            {hasHealing ? formatNumber(spellDetail.healingDone) : '-'}
            {hasHealing ? ' (' + formatPercentage(spellDetail.percentHealingDone) + '%)' : ''}

            {hasOverhealing ? formatNumber(spellDetail.overhealingDone) : '-'}
            {hasOverhealing ? ' (' + formatPercentage(spellDetail.percentOverhealingDone) + '%)' : ''}

          {hasHealing ? formatNumber(spellDetail.hpm) : '-'}
          {hasHealing ? formatNumber(spellDetail.hpet * 1000) : '-'}
        </>
        }
        {!this.state.showHealing &&
        <>

            {hasDamage ? formatNumber(spellDetail.damageDone) : '-'}
            {hasDamage ? ' (' + formatPercentage(spellDetail.percentDamageDone) + '%)' : ''}

          {hasDamage ? formatNumber(spellDetail.dpm) : '-'}
          {hasDamage ? formatNumber(spellDetail.dpet * 1000) : '-'}
        </>
        }
      </>
    );
  };

  componentDidUpdate() {
    ReactTooltip.rebuild();
  }

  render() {
    const { tracker } = this.props;

    return (

                <Toggle
                  defaultChecked={false}
                  icons={false}
                  onChange={event => this.setState({ detailedView: event.target.checked })}
                  id="detailed-toggle"
                />

                  Detailed View

                <Toggle
                  defaultChecked={false}
                  icons={false}
                  onChange={event => this.setState({ showCooldowns: event.target.checked })}
                  id="cooldown-toggle"
                />

                  Show Cooldowns

                  Show Damage

                <Toggle
                  defaultChecked
                  icons={false}
                  onChange={event => this.setState({ showHealing: event.target.checked })}
                  id="healing-toggle"
                />

                  Show Healing

                  Ability
                  {this.state.detailedView ? <this.DetailHeader /> : <this.BarHeader />}

                <this.HealingEfficiencyTable tracker={tracker} showHealing={this.state.showHealing} />

    );
  }
}

export default HealingEfficiencyBreakdown;
<commit_msg>missed feedback<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { formatNumber, formatPercentage } from 'common/format';
import Toggle from 'react-toggle';
import ReactTooltip from 'react-tooltip';
import PerformanceBar from 'common/performanceBar';
import SpellLink from 'common/SpellLink';

class HealingEfficiencyBreakdown extends React.Component {
  static propTypes = {
    tracker: PropTypes.object.isRequired,
  };

  constructor() {
    super();
    this.state = {
      showHealing: true,
      detailedView: false,
      showCooldowns: false,
    };
  }

  HealingEfficiencyTable = (props) => {
    const { tracker } = props;
    const { spells, topHpm, topDpm, topHpet, topDpet } = tracker.getAllSpellStats(this.state.showCooldowns);

    const spellArray = Object.values(spells);

    spellArray.sort((a, b) => {
      if (this.state.showHealing) {
        if (a.hpm  b.hpm) {
          return -1;
        }
      } else {
        if (a.dpm  b.dpm) {
          return -1;
        }
      }

      return 0;
    });

    const spellRows = spellArray.map((spellDetail => {
      if (spellDetail.casts > 0) {
        return this.HealingEfficiencySpellRow(spellDetail, topHpm, topDpm, topHpet, topDpet);
      }
      return null;
    }));

    return spellRows;
  };

  HealingEfficiencySpellRow = (spellDetail, topHpm, topDpm, topHpet, topDpet) => {
    return (

          <SpellLink id={spellDetail.spell.id} />

        {this.state.detailedView ? <this.DetailView spellDetail={spellDetail} /> : <this.BarView spellDetail={spellDetail} topHpm={topHpm} topDpm={topDpm} topHpet={topHpet} topDpet={topDpet} />}

    );
  };

  BarHeader = () => {
    return (
      <>
        Mana Spent
        {this.state.showHealing && (
          <>
            Healing per mana spent
            Healing per second spent casting
          </>
        )}
        {!this.state.showHealing && (
          <>
            Damage per mana spent
            Damage per second spent casting
          </>
        )}
      </>
    );
  };

  BarView = (props) => {
    const { spellDetail, topHpm, topDpm, topHpet, topDpet } = props;
    const hasHealing = spellDetail.healingDone;
    const hasDamage = spellDetail.damageDone > 0;
    const barWidth = 20;

    return (
      <>

          {formatNumber(spellDetail.manaSpent)}
          {' (' + formatPercentage(spellDetail.manaPercentSpent) + '%)'}

        {this.state.showHealing && (
        <>
          {hasHealing ? formatNumber(spellDetail.hpm) : '-'}
          <PerformanceBar percent={spellDetail.hpm / topHpm} />

          {hasHealing ? formatNumber(spellDetail.hpet * 1000) : '-'}
          <PerformanceBar percent={(spellDetail.hpet / topHpet)} />
        </>
        )}
        {!this.state.showHealing && (
        <>
          {hasDamage ? formatNumber(spellDetail.dpm) : '-'}
          <PerformanceBar percent={spellDetail.dpm / topDpm} />

          {hasDamage ? formatNumber(spellDetail.dpet * 1000) : '-'}
          <PerformanceBar percent={(spellDetail.dpet / topDpet)} />
        </>
        )}
      </>
    );
  };

  DetailHeader = () => {
    return (
      <>

          <dfn data-tip={`Total Casts (Number of targets hit)`}>Casts</dfn>

        Mana Spent
        {this.state.showHealing && (
        <>
          Healing Done
          Overhealing

            <dfn data-tip={`Healing per mana spent casting the spell`}>HPM</dfn>

            <dfn data-tip={`Healing per second spent casting the spell`}>HPET</dfn>

        </>
        )}
        {!this.state.showHealing && (
        <>
          Damage Done

            <dfn data-tip={`Damage per mana spent casting the spell`}>DPM</dfn>

            <dfn data-tip={`Damage per second spent casting the spell`}>DPET</dfn>

        </>
          )}
      </>
    );
  };

  DetailView = (props) => {
    const { spellDetail } = props;
    const hasHealing = spellDetail.healingDone;
    const hasOverhealing = spellDetail.healingDone > 0 || spellDetail.overhealingDone > 0;
    const hasDamage = spellDetail.damageDone > 0;

    return (
      <>
        {spellDetail.casts} ({Math.floor(spellDetail.healingHits + spellDetail.damageHits)})

          {formatNumber(spellDetail.manaSpent)}
          {' (' + formatPercentage(spellDetail.manaPercentSpent) + '%)'}

        {this.state.showHealing && (
          <>

              {hasHealing ? formatNumber(spellDetail.healingDone) : '-'}
              {hasHealing ? ' (' + formatPercentage(spellDetail.percentHealingDone) + '%)' : ''}

              {hasOverhealing ? formatNumber(spellDetail.overhealingDone) : '-'}
              {hasOverhealing ? ' (' + formatPercentage(spellDetail.percentOverhealingDone) + '%)' : ''}

            {hasHealing ? formatNumber(spellDetail.hpm) : '-'}
            {hasHealing ? formatNumber(spellDetail.hpet * 1000) : '-'}
          </>
        )}
        {!this.state.showHealing && (
          <>

              {hasDamage ? formatNumber(spellDetail.damageDone) : '-'}
              {hasDamage ? ' (' + formatPercentage(spellDetail.percentDamageDone) + '%)' : ''}

            {hasDamage ? formatNumber(spellDetail.dpm) : '-'}
            {hasDamage ? formatNumber(spellDetail.dpet * 1000) : '-'}
          </>
        )}
      </>
    );
  };

  componentDidUpdate() {
    ReactTooltip.rebuild();
  }

  render() {
    const { tracker } = this.props;

    return (

                <Toggle
                  defaultChecked={false}
                  icons={false}
                  onChange={event => this.setState({ detailedView: event.target.checked })}
                  id="detailed-toggle"
                />

                  Detailed View

                <Toggle
                  defaultChecked={false}
                  icons={false}
                  onChange={event => this.setState({ showCooldowns: event.target.checked })}
                  id="cooldown-toggle"
                />

                  Show Cooldowns

                  Show Damage

                <Toggle
                  defaultChecked
                  icons={false}
                  onChange={event => this.setState({ showHealing: event.target.checked })}
                  id="healing-toggle"
                />

                  Show Healing

                  Ability
                  {this.state.detailedView ? <this.DetailHeader /> : <this.BarHeader />}

                <this.HealingEfficiencyTable tracker={tracker} showHealing={this.state.showHealing} />

    );
  }
}

export default HealingEfficiencyBreakdown;
<|endoftext|>


<commit_before>'use strict';

const randomGeojson = require('randomgeojson');
module.exports = function ({ bbox, model, sensors, lonlat } = {}) {
  const randomGeojsonOptions = { featureTypes: ['Point'] };
  let loc;
  if (lonlat) {
    loc = lonlat;
  } else if (bbox) {
    randomGeojsonOptions.bbox = bbox;
  } else {
    randomGeojsonOptions.bbox = [-179, -89, 179, 89];
  }

  if (!loc) {
    loc = randomGeojson.generateGeoJSON(randomGeojsonOptions).features[0].geometry.coordinates;
  }

  if (!sensors && !model) {
    model = 'homeEthernet';
  }

  const box = {
    'name': 'senseBox',
    'model': model,
    'exposure': 'indoor',
    'weblink': 'https://api.opensensemap.org',
    'location': loc,
    'mqtt': {
      'enabled': false,
      'url': '',
      'topic': '',
      'messageFormat': '',
      'decodeOptions': '',
      'connectionOptions': ''
    }
  };

  if (sensors) {
    box.sensors = sensors;
  }

  return box;
};
<commit_msg>(tests) make name of a box configurable<commit_after>'use strict';

const randomGeojson = require('randomgeojson');
module.exports = function ({ bbox, model, sensors, lonlat, name = '' } = {}) {
  const randomGeojsonOptions = { featureTypes: ['Point'] };
  let loc;
  if (lonlat) {
    loc = lonlat;
  } else if (bbox) {
    randomGeojsonOptions.bbox = bbox;
  } else {
    randomGeojsonOptions.bbox = [-179, -89, 179, 89];
  }

  if (!loc) {
    loc = randomGeojson.generateGeoJSON(randomGeojsonOptions).features[0].geometry.coordinates;
  }

  if (!sensors && !model) {
    model = 'homeEthernet';
  }

  const box = {
    'name': `senseBox${name}`,
    'model': model,
    'exposure': 'indoor',
    'weblink': 'https://api.opensensemap.org',
    'location': loc,
    'mqtt': {
      'enabled': false,
      'url': '',
      'topic': '',
      'messageFormat': '',
      'decodeOptions': '',
      'connectionOptions': ''
    }
  };

  if (sensors) {
    box.sensors = sensors;
  }

  return box;
};
<|endoftext|>


<commit_before>(function() {

	Ti.include('/includes/lib/json.i18n.js');

	exports.getEmptyRow = function() {
		var row = Ti.UI.createTableViewRow({
			editable: false,
			isPlaceholder: true,
			selectionStyle: Ti.UI.iPhone.TableViewCellSelectionStyle.GRAY
		});

		var lbl_title = Ti.UI.createLabel({
			text: I('main.noContent.title'),
			font: {
				fontWeight: 'bold',
				fontSize: 17
			},
			top: 7,
			left: 10,
			right: 10,
			width: Ti.UI.SIZE,
			height: Ti.UI.SIZE
		});

		row.add(lbl_title);

		var lbl_help = Ti.UI.createLabel({
			text: I('main.noContent.help'),
			top: 35,
			bottom: 10,
			left: 10,
			right: 10,
			font: {
				fontSize: 15
			},
			height: Ti.UI.SIZE,
			width: Ti.UI.FILL
		});

		row.add(lbl_help);
		return row;
	}

	exports.getSkinInfoPanel = function(id, description, timestamp, updateSkinsList) {
		var infoPanel = Ti.UI.createTableViewRow({
			height: 215,
			isInfoPanel: true,
			editable: false,
			selectionStyle: Ti.UI.iPhone.TableViewCellSelectionStyle.NONE
		});

		var panelView = Ti.UI.createView({
			height: 215,
			width: 300,
			backgroundImage: '/img/panel_bg.png'
		});

		infoPanel.add(panelView);

		var lbl_skin_desc_title = Ti.UI.createLabel({
			text: I('main.skinDetails.description'),
			top: 20,
			left: 135,
			color: 'darkGray',
			font: {
				fontWeight: 'bold'
			},
			height: 20,
			width: 150
		});

		panelView.add(lbl_skin_desc_title);

		var scrollView_desc = Ti.UI.createScrollView({
			height: 75,
			width: 180,
			top: 45,
			left: 140,
			scrollType: 'vertical',
			showVerticalScrollIndicator: true,
			contentHeight: 'auto'
		});

		panelView.add(scrollView_desc);

		var lbl_skin_desc = Ti.UI.createLabel({
			text: description + '\n ',
			color: 'darkGray',
			top: 0,
			left: 0,
			font: {
				fontSize: 13
			},
			width: 150,
			height: Ti.UI.SIZE
		});

		scrollView_desc.add(lbl_skin_desc);

		var lbl_skin_time_title = Ti.UI.createLabel({
			text: I('main.skinDetails.creation'),
			top: 130,
			left: 135,
			color: 'darkGray',
			font: {
				fontWeight: 'bold'
			},
			height: 20,
			width: 150
		});

		panelView.add(lbl_skin_time_title);

		var creationDate = new Date(timestamp);

		var lbl_skin_time = Ti.UI.createLabel({
			text: creationDate.toLocaleDateString(),
			color: 'darkGray',
			top: lbl_skin_time_title.getTop() + 25,
			left: 140,
			font: {
				fontSize: 13
			},
			height: 'auto',
			width: 150
		});

		panelView.add(lbl_skin_time);

		var b_edit = Ti.UI.createButton({
			bottom: 10,
			right: 10,
			width: 50,
			height: 20,
			title: 'Edit'
		});

		b_edit.addEventListener('click', function(e) {
			var edit_win = Ti.UI.createWindow({
				url: 'add_process/info.js',
				skinIDToEdit: id,
				title: 'Edit skin',
				backgroundImage: Utils.getBGImage(),
				barColor: Utils.getNavColor()
			});

			edit_win.addEventListener('close', updateSkinsList);
			edit_win.open({
				modal: true
			});
		});

		panelView.add(b_edit);

		var view_skin = Ti.UI.createImageView({
			height: 170,
			width: 85,
			top: 20,
			left: 20
		});

		panelView.add(view_skin);

		var img_skin_front = Ti.UI.createImageView({
			defaultImage: '/img/char_front.png',
			image: Ti.Filesystem.getFile(Utils.getSkinsDir() + id + '/front.png').getNativePath(),
			height: 170,
			width: 85,
			top: 0,
			left: 0
		});

		view_skin.add(img_skin_front);

		var img_skin_back = Ti.UI.createImageView({
			defaultImage: '/img/char_back.png',
			image: Ti.Filesystem.getFile(Utils.getSkinsDir() + id + '/back.png').getNativePath(),
			height: 170,
			width: 85,
			top: 0,
			left: 0
		});

		img_skin_front.addEventListener('singletap', function() {
			view_skin.animate({
				view: img_skin_back,
				transition: Ti.UI.iPhone.AnimationStyle.FLIP_FROM_RIGHT
			});
		});

		img_skin_back.addEventListener('singletap', function() {
			view_skin.animate({
				view: img_skin_front,
				transition: Ti.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT
			});
		});

		return infoPanel;
	}

})();
<commit_msg>Style edit button<commit_after>(function() {

	Ti.include('/includes/lib/json.i18n.js');

	exports.getEmptyRow = function() {
		var row = Ti.UI.createTableViewRow({
			editable: false,
			isPlaceholder: true,
			selectionStyle: Ti.UI.iPhone.TableViewCellSelectionStyle.GRAY
		});

		var lbl_title = Ti.UI.createLabel({
			text: I('main.noContent.title'),
			font: {
				fontWeight: 'bold',
				fontSize: 17
			},
			top: 7,
			left: 10,
			right: 10,
			width: Ti.UI.SIZE,
			height: Ti.UI.SIZE
		});

		row.add(lbl_title);

		var lbl_help = Ti.UI.createLabel({
			text: I('main.noContent.help'),
			top: 35,
			bottom: 10,
			left: 10,
			right: 10,
			font: {
				fontSize: 15
			},
			height: Ti.UI.SIZE,
			width: Ti.UI.FILL
		});

		row.add(lbl_help);
		return row;
	}

	exports.getSkinInfoPanel = function(id, description, timestamp, updateSkinsList) {
		var infoPanel = Ti.UI.createTableViewRow({
			height: 215,
			isInfoPanel: true,
			editable: false,
			selectionStyle: Ti.UI.iPhone.TableViewCellSelectionStyle.NONE
		});

		var panelView = Ti.UI.createView({
			height: 215,
			width: 300,
			backgroundImage: '/img/panel_bg.png'
		});

		infoPanel.add(panelView);

		var lbl_skin_desc_title = Ti.UI.createLabel({
			text: I('main.skinDetails.description'),
			top: 20,
			left: 135,
			color: 'darkGray',
			font: {
				fontWeight: 'bold'
			},
			height: 20,
			width: 150
		});

		panelView.add(lbl_skin_desc_title);

		var scrollView_desc = Ti.UI.createScrollView({
			height: 75,
			width: 180,
			top: 45,
			left: 140,
			scrollType: 'vertical',
			showVerticalScrollIndicator: true,
			contentHeight: 'auto'
		});

		panelView.add(scrollView_desc);

		var lbl_skin_desc = Ti.UI.createLabel({
			text: description + '\n ',
			color: 'darkGray',
			top: 0,
			left: 0,
			font: {
				fontSize: 13
			},
			width: 150,
			height: Ti.UI.SIZE
		});

		scrollView_desc.add(lbl_skin_desc);

		var lbl_skin_time_title = Ti.UI.createLabel({
			text: I('main.skinDetails.creation'),
			top: 130,
			left: 135,
			color: 'darkGray',
			font: {
				fontWeight: 'bold'
			},
			height: 20,
			width: 150
		});

		panelView.add(lbl_skin_time_title);

		var creationDate = new Date(timestamp);

		var lbl_skin_time = Ti.UI.createLabel({
			text: creationDate.toLocaleDateString(),
			color: 'darkGray',
			top: lbl_skin_time_title.getTop() + 25,
			left: 140,
			font: {
				fontSize: 13
			},
			height: 'auto',
			width: 150
		});

		panelView.add(lbl_skin_time);

		var b_edit = Ti.UI.createButton({
			top: 20,
			right: 25,
			width: Ti.UI.SIZE,
			height: 20,
			title: '[' + 'Edit' + ']',
			style: Ti.UI.iPhone.SystemButtonStyle.PLAIN,
			color: '#cbcbcb',
			selectedColor: 'gray',
			font: {
				fontSize: 15,
				fontStyle: 'italic'
			}
		});

		b_edit.addEventListener('click', function(e) {
			var edit_win = Ti.UI.createWindow({
				url: 'add_process/info.js',
				skinIDToEdit: id,
				title: 'Edit skin',
				backgroundImage: Utils.getBGImage(),
				barColor: Utils.getNavColor()
			});

			edit_win.addEventListener('close', updateSkinsList);
			edit_win.open({
				modal: true
			});
		});

		panelView.add(b_edit);

		var view_skin = Ti.UI.createImageView({
			height: 170,
			width: 85,
			top: 20,
			left: 20
		});

		panelView.add(view_skin);

		var img_skin_front = Ti.UI.createImageView({
			defaultImage: '/img/char_front.png',
			image: Ti.Filesystem.getFile(Utils.getSkinsDir() + id + '/front.png').getNativePath(),
			height: 170,
			width: 85,
			top: 0,
			left: 0
		});

		view_skin.add(img_skin_front);

		var img_skin_back = Ti.UI.createImageView({
			defaultImage: '/img/char_back.png',
			image: Ti.Filesystem.getFile(Utils.getSkinsDir() + id + '/back.png').getNativePath(),
			height: 170,
			width: 85,
			top: 0,
			left: 0
		});

		img_skin_front.addEventListener('singletap', function() {
			view_skin.animate({
				view: img_skin_back,
				transition: Ti.UI.iPhone.AnimationStyle.FLIP_FROM_RIGHT
			});
		});

		img_skin_back.addEventListener('singletap', function() {
			view_skin.animate({
				view: img_skin_front,
				transition: Ti.UI.iPhone.AnimationStyle.FLIP_FROM_LEFT
			});
		});

		return infoPanel;
	}

})();
<|endoftext|>


<commit_before>require('../jspsych.js');

var data = [
  {rt: 100},
  {rt: 200},
  {rt: 300},
  {rt: 400},
  {rt: 500}
];

jsPsych.data._customInsert(data);

describe('DataCollection', function(){
  test('#mean', function(){
    expect(jsPsych.data.getData().select('rt').mean()).toBe(300);
  });
  test('#count', function(){
    expect(jsPsych.data.getData().select('rt').count()).toBe(5);
  });
  test('#sd', function(){
    expect(jsPsych.data.getData().select('rt').sd()).toBe(Math.sqrt((200^2+100^2)*2));
  });
  test('#median', function(){
    expect(jsPsych.data.getData().select('rt').median()).toBe(300);
  });
  test('#subset', function(){
    expect(jsPsych.data.getData().select('rt').subset(function(x){ return x > 300; }).count()).toBe(2);
  })
});
<commit_msg>fix sd test<commit_after>require('../jspsych.js');

var data = [
  {rt: 100},
  {rt: 200},
  {rt: 300},
  {rt: 400},
  {rt: 500}
];

jsPsych.data._customInsert(data);

describe('DataCollection', function(){
  test('#mean', function(){
    expect(jsPsych.data.getData().select('rt').mean()).toBe(300);
  });
  test('#count', function(){
    expect(jsPsych.data.getData().select('rt').count()).toBe(5);
  });
  test('#sd', function(){
    expect(jsPsych.data.getData().select('rt').sd()).toBe(Math.sqrt((200^2+100^2+100^2+200^2)/5));
  });
  test('#median', function(){
    expect(jsPsych.data.getData().select('rt').median()).toBe(300);
  });
  test('#subset', function(){
    expect(jsPsych.data.getData().select('rt').subset(function(x){ return x > 300; }).count()).toBe(2);
  })
});
<|endoftext|>


<commit_before><commit_msg>ShaderChunk: Fixed spotLight.color double-counting.<commit_after><|endoftext|>