Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/landing/main/src/ |
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/landing/main/src/external.controls.js |
import { Dom } from 'main.core'; import { Main } from './main'; type Block = { id: number, state: boolean, permissions: { allowDesignBlock: boolean, allowModifyStyles: boolean, allowEditContent: boolean, allowSorting: boolean, allowRemove: boolean, allowChangeState: boolean, }, }; type MobileTop = { blockId: number, top: number, height: number, }; export class ExternalControls { #postMessages = { mode: 'mode', register: 'register', changeState: 'changestate', editorEnable: 'editorenable', showControls: 'showcontrols', showBlockControls: 'showblockcontrols', hideAll: 'hideall', backendAction: 'backendaction', }; #currentMobileTop: number = -1; #mouseEntered: boolean = false; #disableControls: boolean = false; #currentMousePosition: number = 0; #blocksMobileTops: Array<MobileTop> = []; constructor() { if (Main.isExternalControlsEnabled()) { this.#registerListeners(); } } /** * Registers all required listeners. */ #registerListeners() { setTimeout(() => { this.#registerBlocks(); }, 0); // listening commands from outer frame window.addEventListener('message', (event) => { if (this.isControlsExternal()) { this.listenExternalCommands(event.data.action, event.data.payload); } }); // catching the mouse and scrolling document.addEventListener('mouseenter', (event) => { this.#mouseEntered = true; }); document.addEventListener('mouseleave', (event) => { this.#mouseEntered = false; }); document.addEventListener('mousemove', (event) => { this.onMobileMouseMove(event.y); }); document.addEventListener('scroll', () => { if (this.#mouseEntered) { this.recalculateTopsIfExternals(); } }); // checking when external commands become enabled BX.addCustomEvent('BX.Landing.Main:changeControls', (type, topInPercent) => { if (type === 'internal') { this.postExternalCommand(this.#postMessages.hideAll, {}); } else { // mode switching some time setTimeout(() => { this.recalculateTops(true); }, 400); } }); // checking inline editor — enabled or disabled BX.addCustomEvent('BX.Landing.Editor:enable', () => { this.#disableControls = true; if (this.isControlsExternal()) { this.postExternalCommand(this.#postMessages.hideAll, {}); } }); BX.addCustomEvent('BX.Landing.Editor:disable', () => { this.#disableControls = false; this.recalculateTopsIfExternals(true); }); // checking that new block was added and any block changed its active status BX.addCustomEvent('BX.Landing.Block:onAfterAdd', (event) => { setTimeout(() => { const blockData = event.getData(); this.#registerNewBlock(blockData.id); }, 500); }); BX.addCustomEvent('BX.Landing.Block:changeState', (blockId, state) => { this.postExternalCommand(this.#postMessages.changeState, { blockId, state }); }); // form's settings were opened and then closed BX.addCustomEvent('BX.Landing.Block:onFormSettingsOpen', () => { if (this.isControlsExternal()) { this.postExternalCommand(this.#postMessages.hideAll, {}); } this.#disableControls = true; }); BX.addCustomEvent('BX.Landing.Block:onFormSettingsClose', (blockId) => { // after form completely closed setTimeout(() => { this.#disableControls = false; this.recalculateTopsIfExternals(true); }, 400); this.postExternalCommand(this.#postMessages.hideAll, {}); }); BX.addCustomEvent('BX.Landing.Block:onAfterFormSave', (blockId) => { setTimeout(() => { this.postExternalCommand(this.#postMessages.backendAction, { action: 'Landing\\Block::saveForm', data: {block: blockId}, }); }, 1000); }); BX.addCustomEvent('BX.Landing.Block:onBlockEditClose', () => { this.#disableControls = false; this.recalculateTopsIfExternals(true); }); BX.addCustomEvent('BX.Landing.Block:onContentSave', this.recalculateTopsIfExternals.bind(this)); BX.addCustomEvent('BX.Landing.Block:onDesignerBlockSave', this.recalculateTopsIfExternals.bind(this)); BX.addCustomEvent('BX.Landing.Block:Card:add', this.recalculateTopsIfExternals.bind(this)); BX.addCustomEvent('BX.Landing.Block:Card:remove', this.recalculateTopsIfExternals.bind(this)); BX.addCustomEvent('BX.Landing.Block:afterRemove', this.recalculateTopsIfExternals.bind(this)); BX.addCustomEvent('BX.Landing.Backend:action', this.onBackendAction.bind(this)); BX.addCustomEvent('BX.Landing.Backend:batch', this.onBackendAction.bind(this)); } /** * Invokes when backend action occurred. */ onBackendAction(action, data) { this.#disableControls = false; this.postExternalCommand(this.#postMessages.backendAction, { action, data }); } /** * Creates and returns Block object for sending to external window. * * @param {BX.Landing.} block * @return {Block} */ #createBlockObject(block: BX.Landing.Block): Block { return { id: parseInt(block.id), state: block.isEnabled(), permissions: { allowDesignBlock: block.isDesignBlockAllowed(), allowModifyStyles: block.isStyleModifyAllowed(), allowEditContent: block.isEditBlockAllowed(), allowSorting: block.isEditBlockAllowed(), allowRemove: block.isRemoveBlockAllowed(), allowChangeState: block.isChangeStateBlockAllowed(), allowPaste: block.isPasteBlockAllowed(), allowSaveInLibrary: block.isSaveBlockInLibraryAllowed(), } }; } /** * Registers all blocks on entire page. */ #registerBlocks() { const blocksCollection = BX.Landing.PageObject.getBlocks(); const data = []; [...blocksCollection].map(block => data.push(this.#createBlockObject(block))); this.postExternalCommand(this.#postMessages.register, { blocks: data, }); } /** * Registers new block. * * @param {number} blockId */ #registerNewBlock(blockId: number) { const block = BX.Landing.PageObject.getBlocks().get(blockId); if (block) { this.postExternalCommand(this.#postMessages.register, { blocks: [this.#createBlockObject(block)], }); // because new block adding some time if (this.isControlsExternal()) { this.recalculateTops(); } else { this.postExternalCommand(this.#postMessages.hideAll, {}); } } } /** * Checks that landing controls is external * * @return {boolean} */ isControlsExternal() { return Dom.hasClass(document.body, 'landing-ui-external-controls'); } /** * Recalculates block tops. * * @param {boolean} resetMobileTop */ recalculateTops(resetMobileTop: boolean) { this.#blocksMobileTops = []; if (resetMobileTop) { this.#currentMobileTop = -1; } [...document.body.querySelectorAll('.block-wrapper')].map(block => { const blockRect = block.getBoundingClientRect(); if (blockRect.height > 1)// hidden on mobile blocks { this.#blocksMobileTops.push({ blockId: parseInt(block.getAttribute('data-id')), top: blockRect.top, height: blockRect.height, }); } }); this.onMobileMouseMove(this.#currentMousePosition); } /** * Recalculates block tops only if external controls are enabled. * * @param {boolean} resetMobileTop */ recalculateTopsIfExternals(resetMobileTop: boolean) { if (this.isControlsExternal()) { this.recalculateTops(resetMobileTop); } } /** * Call when user moves mouse over the mobile page. * * @param {number} top */ onMobileMouseMove(top: number) { if (this.#disableControls || !this.isControlsExternal()) { return; } if (top <= 0) { this.#currentMobileTop = -1; return; } this.#currentMousePosition = top; for (let i = 0, c = this.#blocksMobileTops.length; i < c; i++) { if ( top >= this.#blocksMobileTops[i]['top'] && (!this.#blocksMobileTops[i+1] || top < this.#blocksMobileTops[i+1]['top']) ) { if (this.#blocksMobileTops[i]['top'] !== this.#currentMobileTop) { this.#currentMobileTop = this.#blocksMobileTops[i]['top']; this.postExternalCommand(this.#postMessages.showControls, { blockId: this.#blocksMobileTops[i]['blockId'], top: this.#blocksMobileTops[i]['top'], height: this.#blocksMobileTops[i]['height'], }); } break; } } } /** * Sends action with payload to parent window. * * @param {string} action * @param {Object} payload */ postExternalCommand(action, payload) { if (window.parent) { window.parent.postMessage({action, payload}, window.location.origin); } } /** * Receives actions with payload from parent window. * * @param {string} action * @param {Object} payload */ listenExternalCommands(action, payload) { const block = BX.Landing.PageObject.getBlocks().get( payload?.blockId ? payload.blockId : -1 ); if (payload?.blockId && !block) { return; } const successCallback = () => { setTimeout(() => { this.#currentMousePosition = 0; this.recalculateTops(); }, 300); }; switch (action) { case 'onDesignerBlockClick': { block.onDesignerBlockClick(); break; } case 'onEditBlockClick': { block.onShowContentPanel(); break; } case 'onStyleBlockClick': { block.onStyleShow(); break; } case 'onSortDownBlockClick': { block.moveDown(); successCallback(); break; } case 'onSortUpBlockClick': { block.moveUp(); successCallback(); break; } case 'onRemoveBlockClick': { block.deleteBlock(); break; } case 'onChangeStateBlockClick': { block.onStateChange(); break; } case 'onCutBlockClick': { Main.getInstance().onCutBlock.bind(Main.getInstance(), block)(); break; } case 'onCopyBlockClick': { Main.getInstance().onCopyBlock.bind(Main.getInstance(), block)(); break; } case 'onPasteBlockClick': { Main.getInstance().onPasteBlock.bind( Main.getInstance(), block, (blockId) => { setTimeout(() => { this.#registerNewBlock(blockId); }, 300); } )(); break; } case 'onFeedbackClick': { block.showFeedbackForm(); break; } case 'onSaveInLibraryClick': { block.saveBlock(); break; } case 'onHideEditorPanel': { BX.Landing.UI.Panel.EditorPanel.getInstance().hide(); break; } } } }