<commit_before>import _ from "underscore";
import BaseView from "./BaseView";
import getContextOptions from "./utils/getContextOptions";

let callbackIdGenerator = 0;
let emptyImage = 'empty.gif';
let cache = [];
let timeout;

/**
 * Class rappresenting an image. It support placeholder, resize and positioning.
 * Always use this class to prevent memory leaks.
 * @version 2.0.0
 * @param {Object} options - Page options.
 * @param {string} options.url - Specifies the URL of an image.
 * @param {string} [options.placeholder] ['empty.gif'] - Temporary image, default is a transparent one pixel image named 'empty.gif' that should be placed on the root of the build dir.
 * @param {bool} [options.autoload] [true] - Load image immediatly. If false the method load() must be called manually.
 * @param {string} [options.size] ['auto'] - Specify how to resize the image based on the container.<br />
 *   `normal`: the image is not resized.<br />
 *   `contain`: the image is scaled by the longest side.<br />
 *   `cover`: the image is scaled by the shortest side.<br />
 *   `auto`: if the image is vertical applies the `cover` style, if horizontal it applies the `contain` style.
 * @param {string} [options.className] ['image'] - Name of CSS class added to the image
 * @param {string} [options.position] ['center'] - It indicates how to place the image. It will be used as `className`. The values are `center` or `top-left`.
 * @param {Object} [options.viewport] - It's the size of the image container. If not provided it will be calculated but with performance issue, try to always fulfill 'viewport'.
 * @param {integer} options.viewport.width - Width of the container.
 * @param {integer} options.viewport.height - Height of the container.
 * @param {integer} [options.orientation] [0] - Orientation in degrees of the image.
 * @param {string} [options.empty] [empty.gif] - Empty image to use when loaded image fails.
 *
 * @fires ImageView#loaded
 * @fires ImageView#error
 *
 * @example
 * import { BaseView, ImageView } from 'backbone.uikit';
 * export default class MyView extends BaseView {
 *   constructor(options) {
 *     super(options);
 *     this.addSubView('coverImage', new ImageView({
 *       src: 'http://placehold.it/350x150',
 *       placeholder: 'img/placeholder-cover-image.png',
 *       size: 'auto',
 *       viewport: { width: 250, height: 100 }
 *     });
 *   }
 *
 *   onRender(rendered) {
 *     if (rendered)
 *       return this;
 *     let coverImage = this.getSubView('coverImage');
 *     this.$el.append(coverImage.el);
 *     coverImage.render();
 *     return this;
 *   }
 * }
 * @example <caption>HTML Output</caption>
 * <div class="my-view">
 *   <figure class="ui-base-view ui-image">
 *     <img src="http://placehold.it/350x150" style="position: absolute; top: 50%; left: 50%; width: 250; height: 100px; margin-top: -50px; margin-left: -125px; opacity: 1;">
 *   </figure>
 * </div>
 */
export default class ImageView extends BaseView {

	/**
	 * Triggered when an image is successfully loaded.
	 * @event ImageView#loaded
	 */

	/**
	 * Triggered when an image fail to load.
	 * @event ImageView#error
	 * @property {string} src - Source of the image
	 */

	/**
	 * Define the HTML tag of the view.
	 * @private
	 * @version 2.0.0
	 */
	tagName() {
		return 'figure';
	}

	constructor(options) {
		super(options);

		this._loaded = false;
		this._loadDebounce = null;
		this._appended = false;

		this.src = '';

		this.setDefaultsOptions(getContextOptions('ImageView'), {
			src: null,
			autoload: true,
			placeholder: null,
			size: 'auto', // auto, normal, contain, cover. Scale the image to its container
			worker: false,
			viewport: null,
			className: 'ui-image',
			position: 'center', // top-left, top, top-right, center-left, center, center-right, bottom-left, bottom, bottom-right
			orientation: 0,
			empty: emptyImage
		});

		this.$el.addClass(_.result(this.options, 'className') || '');

		if (this.options.size !== 'normal')
			this.$el.addClass(' ' + _.result(this.options, 'position') || '');

		this.image = new Image();
		this.image.style.opacity = 0;

		// Placeholder
		this.placeholderImage = new Image();
		this.placeholderImage.onload = () => {
			this._placeholderLoaded = true;
			if (!this._loaded) {
				this.requestAnimationFrame(() => {
					this.image.src = this.placeholderImage.src;
					this.requestAnimationFrame(() => {
						if ( this.rendered )
							this.render();
					});
				});
			}
		};

		if (this.options.placeholder) {
			this.placeholderImage.src = this.options.placeholder;
		}

		this.bufferImage = new Image();

		// Normal
		this.bufferImage.onerror = (err) => {
			this._loaded = false;
			this.requestAnimationFrame(() => {
				this.trigger('error', this.image.src);
				this.image.src = this.options.placeholder || this.options.empty;
			});
		};
		this.bufferImage.onload = () => {
			this._loaded = true;
			this.requestAnimationFrame(() => {
				this.image.src = this.src;
				this.requestAnimationFrame(() => {
					if ( this.rendered )
						this.render();
					this.trigger('loaded');
				});
			});
		};

		this.setSource(this.options.src);
	}

	/**
	 * Load image.
	 * @public
	 * @version 2.0.0
	 */
	load() {
		if (this.src == this.bufferImage.src)
			return;
		this._loaded = false;
		if (this._loadDebounce)
			this.cancelAnimationFrame(this._loadDebounce);
		this._loadDebounce = this.requestAnimationFrame(() => {
			this.bufferImage.src = this.src;
		});
	}

	/**
	 * Set a new image with orientation.
	 * @public
	 * @version 2.0.0
	 * @param {string} src - URL of the new image.
	 * @param {integer} orientation [options.orientation] - Orientation of the new image. Default use options.orientation.
	 */
	setSource(src, orientation) {
		if (this.src == src) return;
		if (typeof orientation !== 'undefined') {
			this.options.orientation = orientation;
		}
		if (this._loadDebounce) {
			this.cancelAnimationFrame(this._loadDebounce);
			this._loadDebounce = null;
		}
		this.src = src;
		this.image.style.opacity = 0;
		this.image.src = this.options.empty;
		this.bufferImage.src = this.options.empty;
		if (this.options.autoload) {
			this.load();
			return;
		}
	}

	/**
	 * Set a new placeholder with orientation.
	 * @public
	 * @version 2.0.0
	 * @param {string} src - URL of the new image.
	 * @param {integer} orientation [options.orientation] - Orientation of the new image. Default use options.orientation.
	 */
	setPlaceholder(src, orientation) {
		if (this.placeholderImage.src == src) return;
		if (typeof orientation !== 'undefined') {
			this.options.orientation = orientation;
		}
		if (this._loadDebounce)
			this.cancelAnimationFrame(this._loadDebounce);
		this.placeholderImage.src = src;
		this.image.style.opacity = 0;
		this.image.src = this.options.empty;
	}

	/**
	 * Called by BaseView when the image should render.<commit_msg>If setSource with null o undefined the src set into empty image<commit_after>import _ from "underscore";
import BaseView from "./BaseView";
import getContextOptions from "./utils/getContextOptions";

let callbackIdGenerator = 0;
let emptyImage = 'empty.gif';
let cache = [];
let timeout;

/**
 * Class rappresenting an image. It support placeholder, resize and positioning.
 * Always use this class to prevent memory leaks.
 * @version 2.0.0
 * @param {Object} options - Page options.
 * @param {string} options.url - Specifies the URL of an image.
 * @param {string} [options.placeholder] ['empty.gif'] - Temporary image, default is a transparent one pixel image named 'empty.gif' that should be placed on the root of the build dir.
 * @param {bool} [options.autoload] [true] - Load image immediatly. If false the method load() must be called manually.
 * @param {string} [options.size] ['auto'] - Specify how to resize the image based on the container.<br />
 *   `normal`: the image is not resized.<br />
 *   `contain`: the image is scaled by the longest side.<br />
 *   `cover`: the image is scaled by the shortest side.<br />
 *   `auto`: if the image is vertical applies the `cover` style, if horizontal it applies the `contain` style.
 * @param {string} [options.className] ['image'] - Name of CSS class added to the image
 * @param {string} [options.position] ['center'] - It indicates how to place the image. It will be used as `className`. The values are `center` or `top-left`.
 * @param {Object} [options.viewport] - It's the size of the image container. If not provided it will be calculated but with performance issue, try to always fulfill 'viewport'.
 * @param {integer} options.viewport.width - Width of the container.
 * @param {integer} options.viewport.height - Height of the container.
 * @param {integer} [options.orientation] [0] - Orientation in degrees of the image.
 * @param {string} [options.empty] [empty.gif] - Empty image to use when loaded image fails.
 *
 * @fires ImageView#loaded
 * @fires ImageView#error
 *
 * @example
 * import { BaseView, ImageView } from 'backbone.uikit';
 * export default class MyView extends BaseView {
 *   constructor(options) {
 *     super(options);
 *     this.addSubView('coverImage', new ImageView({
 *       src: 'http://placehold.it/350x150',
 *       placeholder: 'img/placeholder-cover-image.png',
 *       size: 'auto',
 *       viewport: { width: 250, height: 100 }
 *     });
 *   }
 *
 *   onRender(rendered) {
 *     if (rendered)
 *       return this;
 *     let coverImage = this.getSubView('coverImage');
 *     this.$el.append(coverImage.el);
 *     coverImage.render();
 *     return this;
 *   }
 * }
 * @example <caption>HTML Output</caption>
 * <div class="my-view">
 *   <figure class="ui-base-view ui-image">
 *     <img src="http://placehold.it/350x150" style="position: absolute; top: 50%; left: 50%; width: 250; height: 100px; margin-top: -50px; margin-left: -125px; opacity: 1;">
 *   </figure>
 * </div>
 */
export default class ImageView extends BaseView {

	/**
	 * Triggered when an image is successfully loaded.
	 * @event ImageView#loaded
	 */

	/**
	 * Triggered when an image fail to load.
	 * @event ImageView#error
	 * @property {string} src - Source of the image
	 */

	/**
	 * Define the HTML tag of the view.
	 * @private
	 * @version 2.0.0
	 */
	tagName() {
		return 'figure';
	}

	constructor(options) {
		super(options);

		this._loaded = false;
		this._loadDebounce = null;
		this._appended = false;

		this.src = '';

		this.setDefaultsOptions(getContextOptions('ImageView'), {
			src: null,
			autoload: true,
			placeholder: null,
			size: 'auto', // auto, normal, contain, cover. Scale the image to its container
			worker: false,
			viewport: null,
			className: 'ui-image',
			position: 'center', // top-left, top, top-right, center-left, center, center-right, bottom-left, bottom, bottom-right
			orientation: 0,
			empty: emptyImage
		});

		this.$el.addClass(_.result(this.options, 'className') || '');

		if (this.options.size !== 'normal')
			this.$el.addClass(' ' + _.result(this.options, 'position') || '');

		this.image = new Image();
		this.image.style.opacity = 0;

		// Placeholder
		this.placeholderImage = new Image();
		this.placeholderImage.onload = () => {
			this._placeholderLoaded = true;
			if (!this._loaded) {
				this.requestAnimationFrame(() => {
					this.image.src = this.placeholderImage.src;
					this.requestAnimationFrame(() => {
						if ( this.rendered )
							this.render();
					});
				});
			}
		};

		if (this.options.placeholder) {
			this.placeholderImage.src = this.options.placeholder;
		}

		this.bufferImage = new Image();

		// Normal
		this.bufferImage.onerror = (err) => {
			this._loaded = false;
			this.requestAnimationFrame(() => {
				this.trigger('error', this.image.src);
				this.image.src = this.options.placeholder || this.options.empty;
			});
		};
		this.bufferImage.onload = () => {
			this._loaded = true;
			this.requestAnimationFrame(() => {
				this.image.src = this.src;
				this.requestAnimationFrame(() => {
					if ( this.rendered )
						this.render();
					this.trigger('loaded');
				});
			});
		};

		this.setSource(this.options.src);
	}

	/**
	 * Load image.
	 * @public
	 * @version 2.0.0
	 */
	load() {
		if (this.src == this.bufferImage.src)
			return;
		this._loaded = false;
		if (this._loadDebounce)
			this.cancelAnimationFrame(this._loadDebounce);
		this._loadDebounce = this.requestAnimationFrame(() => {
			this.bufferImage.src = this.src;
		});
	}

	/**
	 * Set a new image with orientation.
	 * @public
	 * @version 2.0.0
	 * @param {string} src - URL of the new image.
	 * @param {integer} orientation [options.orientation] - Orientation of the new image. Default use options.orientation.
	 */
	setSource(src, orientation) {
		if (this.src == src) return;
		if (typeof orientation !== 'undefined') {
			this.options.orientation = orientation;
		}
		if (this._loadDebounce) {
			this.cancelAnimationFrame(this._loadDebounce);
			this._loadDebounce = null;
		}
		if ( _.isNull(src) || _.isUndefined(src) )
			src = this.options.empty;
		this.src = src;
		this.image.style.opacity = 0;
		this.image.src = this.options.empty;
		this.bufferImage.src = this.options.empty;
		if (this.options.autoload) {
			this.load();
			return;
		}
	}

	/**
	 * Set a new placeholder with orientation.
	 * @public
	 * @version 2.0.0
	 * @param {string} src - URL of the new image.
	 * @param {integer} orientation [options.orientation] - Orientation of the new image. Default use options.orientation.
	 */
	setPlaceholder(src, orientation) {
		if (this.placeholderImage.src == src) return;
		if (typeof orientation !== 'undefined') {
			this.options.orientation = orientation;
		}
		if (this._loadDebounce)
			this.cancelAnimationFrame(this._loadDebounce);
		this.placeholderImage.src = src;
		this.image.style.opacity = 0;
		this.image.src = this.options.empty;
	}

	/**
	 * Called by BaseView when the image should render.
{'fraction_non_alphanumeric': 0.14442044636429086, 'fraction_numerical': 0.007919366450683946, 'mean_word_length': 3.292645241038319, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '757780', 'n_tokens_mistral': 4840, 'n_tokens_neox': 4242, 'n_words': 1231}