Your IP : 3.136.17.118


Current Path : /var/www/axolotl/data/www/axolotl.ru/www/bitrix/js/yandexpaypay/widget/
Upload File :
Current File : /var/www/axolotl/data/www/axolotl.ru/www/bitrix/js/yandexpaypay/widget/factory.js

import SolutionRegistry from './solutionregistry';
import NodePreserver from "./ui/nodepreserver";
import Utils from './utils/template';
import {EventProxy} from "./utils/eventproxy";
import {Sdkloader} from "./sdkloader";
import {Intersection} from "./intersection";
import Display from "./ui/display/factory";
import {Concurrency} from "./concurrency";

export default class Factory {

	static defaults = {
		solution: null,
		template:
			'<div id="#ID#" ' +
				'class="bx-yapay-drawer-container ' +
				'yapay-behavior--#MODE# ' +
				'yapay-display--#DISPLAY# #WIDGET_TYPE# '+
				'yapay-width--#WIDTH# ' +
				'yapay-solution--#SOLUTION#">' +
				'#STYLE#' +
				'<div class="bx-yapay-drawer"></div>' +
			'</div>',
		useDivider: false,
		containerSelector: '.bx-yapay-drawer',
		event: null,
		eventConfig: {},
		preserve: {
			composite: true,
		},
		waitLimit: 30,
		waitTimeout: 1000,
	}

	defaults;
	options;
	waitCount = 0;
	solution;

	constructor(options = {}) {
		this.defaults = Object.assign({}, this.constructor.defaults);
		this.options = {};

		this.setOptions(options);
		this.createSolution();
		this.bootSolution();
		this.bootLocal();
	}

	inject(selector, position) {
		let selectorSanitized;

		return Promise.resolve()
			.then(() => {
				selectorSanitized = this.filterMedia(selector);
			})
			.then(() => this.waitEvent())
			.then(() => this.waitElement(selectorSanitized))
			.then((anchor) => this.checkElement(anchor))
			.then((anchor) => this.renderElement(anchor, position))
			.then((element) => this.install(element))
			.then((widget) => this.insertToolsDisplay(widget))
			.then((widget) => {
				const intersection = new Intersection(widget.el);
				
				if (this.getOption('preserve')) {
					this.preserve(selectorSanitized, position, widget, intersection);
				}

				return intersection.wait().then(() => widget);
			})
			.then((widget) => this.testConcurrency(widget))
			.then((widget) => Sdkloader.getInstance().load().then(() => widget));
	}

	filterMedia(selector: string) : string {
		const resultParts = [];

		for (const part of selector.split(',')) {
			const partSanitized = part.trim();

			if (partSanitized === '') { continue; }

			const [partSelector, matchMedia] = this.testSelectorMedia(partSanitized);

			if (!matchMedia) { continue; }

			resultParts.push(partSelector);
		}

		if (resultParts.length === 0) {
			throw new Error('widget not matched any media of ' + selector);
		}

		return resultParts.join(',');
	}

	checkElement(anchor) {
		const selector = this.containerSelector();
		const contains = (
			!!anchor.querySelector(selector)
			|| this.containsSiblingElement(anchor, selector)
		);

		if (contains) {
			throw new Error('the element already has a container');
		}

		return anchor;
	}

	containerSelector() {
		return '#' + this.getOption('containerId') + ' ' + this.getOption('containerSelector');
	}

	containsSiblingElement(anchor, selector) {
		let result = false;
		let next = anchor.parentElement?.firstElementChild;

		while (next) {
			if (next.matches(selector) || next.querySelector(selector)) {
				result = true;
				break;
			}

			next = next.nextElementSibling;
		}

		return result;
	}
	
	preserve(selector, position, widget, intersection) {
		const preserver = new NodePreserver(widget.el, Object.assign({}, this.preserveOptions(), {
			restore: () => {
				preserver.destroy();
				// noinspection JSIgnoredPromiseFromCall
				this.restore(selector, position, widget, intersection);
			},
		}));

		widget.setPreserver(preserver);
	}

	preserveOptions() {
		const preserveOption = this.getOption('preserve');

		return typeof preserveOption === 'object' ? preserveOption : {};
	}

	restore(selector, position, widget, intersection) {
		return Promise.resolve()
			.then(() => this.waitElement(selector))
			.then((anchor) => {
				const element = this.renderElement(anchor, position);

				widget.restore(element);
				intersection?.restore(element);

				if (this.getOption('preserve')) {
					this.preserve(selector, position, widget, intersection);
				}

				return widget;
			});
	}

	testConcurrency(widget) {
		const mode = this.getOption('mode');

		if (mode == null) { return widget; }

		for (const sibling of Concurrency.same(mode)) {
			if (document.documentElement.contains(sibling.el)) { continue; }

			sibling.destroy();
			Concurrency.pop(mode, sibling);
		}

		Concurrency.push(mode, widget);

		return widget;
	}

	install(element) {
		const widget = new BX.YandexPay.Widget(element, this.options);

		widget.setSolution(this.solution);
		widget.boot();

		return widget;
	}

	insertToolsDisplay(widget) {
		widget.bootToolsDisplay();
		return widget;
	}

	waitEvent() {
		const name = this.getOption('event');

		if (name == null) { return null; }

		return new Promise((resolve) => {
			const eventProxy = EventProxy.make(this.getOption('eventConfig'));
			const callback = () => {
				eventProxy.off(name, callback);
				resolve();
			};

			eventProxy.on(name, callback);
		});
	}

	waitElement(selector) {
		return new Promise((resolve, reject) => {
			this.waitCount = 0;
			this.waitElementLoop(selector, resolve, reject);
		});
	}

	waitElementLoop(selector, resolve, reject) {
		const anchor = this.findElement(selector);

		if (anchor) {
			resolve(anchor);
			return;
		}

		++this.waitCount;

		if (this.waitCount >= this.getOption('waitLimit')) {
			reject('cant find element by selector ' + selector);
			return;
		}

		setTimeout(this.waitElementLoop.bind(this, selector, resolve, reject), this.getOption('waitTimeout'));
	}

	findElement(selector) {
		let elementList;
		let variant = selector.trim();
		let result;

		if (variant === '') { throw new Error('widget selector is empty'); }

		elementList = this.searchBySelector(variant) ?? this.searchById(selector) ?? this.searchByClassName(selector);

		if (elementList == null) { return null; }

		if (elementList.length > 1) {
			result = this.reduceVisible(elementList);
		}

		if (result == null) {
			result = elementList[0];
		}

		return result;
	}

	searchBySelector(selector) {
		try {
			const result = [];

			for (const part of selector.split(',')) { // first selector
				const partSanitized = part.trim();

				if (partSanitized === '' || !this.isCssSelector(partSanitized)) { continue; }

				const collection = document.querySelectorAll(partSanitized);

				for (const element of collection) {
					result.push(element);
				}
			}

			return result.length > 0 ? result : null;
		} catch (e) {
			return null;
		}
	}

	searchById(selector) {
		try {
			const element = document.getElementById(selector);

			return element != null ? [element] : null;
		} catch (e) {
			return null;
		}
	}

	searchByClassName(selector) {
		try {
			const collection = document.getElementsByClassName(selector);

			return collection.length > 0 ? collection : null;
		} catch (e) {
			return null;
		}
	}

	reduceVisible(collection) {
		let result = null;
		
		for (const element of collection) {
			if (this.testVisible(element)) {
				result = element;
				break;
			}
		}

		return result;
	}
	
	testVisible(element) {
		return (element.offsetWidth || element.offsetHeight || element.getClientRects().length );
	}

	isCssSelector(selector) {
		return /^[.#]/.test(selector);
	}

	testSelectorMedia(selector) {
		const partials = /^(.*):media(\(.*\))$/.exec(selector);

		if (partials == null) { return [ selector, true ]; }

		return [
			partials[1],
			window.matchMedia(partials[2]).matches,
		];
	}

	renderElement(anchor, position) {
		const selector = this.containerSelector();
		const display = this.getDisplay();
		const displayType = this.getOption('displayType')?.toLowerCase();
		let widgetType = '';

		if (displayType === 'widget') {
			widgetType = display.getOption('TYPE_WIDGET')?.toLowerCase();
		}

		const html = Utils.compile(this.getOption('template'), {
			style: display != null ? display.style() : '',
			width: display != null ? display.width().toLowerCase() : 'auto',
			id: this.getOption('containerId'),
			mode: this.getOption('mode') || 'payment',
			display: this.getOption('displayType')?.toLowerCase() || 'button',
			solution: this.getOption('solution')?.toLowerCase(),
			widget_type: widgetType,
		});

		let elements = Utils.toElements(html);
		let result = null;

		if (position.indexOf('after') === 0) { elements = elements.reverse(); }

		for (const element of elements) {
			anchor.insertAdjacentElement(position, element);

			if (result != null) { continue; }

			result = element.matches(selector) ? element : element.querySelector(selector);
		}

		if (result == null) {
			throw new Error(`cant find template container by selector ${selector}`);
		}

		return result;
	}

	createSolution() {
		const name = this.getOption('solution');
		const mode = this.getOption('mode');

		this.solution = SolutionRegistry.createPage(name, mode);
	}

	bootSolution() {
		this.solution?.bootFactory(this);
	}

	getDisplay() {
		const type = this.getOption('displayType');
		const options = this.getOption('displayParameters');

		if (type == null) { return null; }

		return Display.make(type, this, options);
	}

	getDivider() {
		return this.getOption('useDivider')
			? Utils.compile(this.getOption('divider'), {label: this.getOption('label')})
			: '';
	}

	bootLocal() {
		EventProxy.make().fire('bxYapayFactoryInit', {
			factory: this,
		});
	}

	extendDefaults(options) {
		this.defaults = Object.assign(this.defaults, options);
	}

	setOptions(options) {
		this.options = Object.assign(this.options, options);
	}

	getOption(name) {
		return this.options[name] ?? this.defaults[name];
	}
}