activator-oce-exporter
Version:
Extract Activator binder and convert it to valid OCE mono pacakge
410 lines (368 loc) • 12 kB
JavaScript
import { FusionStore } from './services/fusion-store';
import { FusionAligner } from './services/fusion-aligner';
import { FusionNavigation } from './navigation';
import {
freeze, unfreeze, setActivatorEnv, toggleState,
} from './_actions/app.js';
import { themeApplier } from './theme-applier';
import { editorMessenger } from './editor-messenger';
import { monitoring } from './monitoring.js';
import { FusionLogger } from './services/fusion-logger';
import { FusionBase } from './base';
import { rootSelector } from '../config.json';
// This is the API accessible from browser via window.Fusion namespace
/* Private methods start */
const setDefaultTemplate = ({ template = '', element }) => {
element.innerHTML += template;
};
const getComponentState = (el) => {
let state = null;
if (el.state) {
const name = `${el.state}-${el.id}`;
const { currentState } = FusionStore.store.getState().app;
const isActive = currentState.includes(name);
state = { name, isActive };
}
return state;
};
const generateAddedElementData = ({ element, query, options }) => ({
name: 'fusion/saveAddedComponent',
data: {
query,
options,
state: getComponentState(element),
template: element.outerHTML,
selectorId: element.id,
tagName: element.tagName,
},
});
const generateRemovedElementData = ({ query, deselectedElement, state }) => ({
name: 'fusion/saveRemovedComponent',
data: {
query,
state,
deselectedElement,
},
});
const generateUpdateContentData = data => ({
name: 'actions/updateContent',
data,
});
const generateSaveAttributesData = data => ({
name: 'actions/saveAttributes',
data,
});
const generateSaveStylesData = data => ({
name: 'actions/saveStyles',
data,
});
const shouldAdjustState = (state, enforcedState) => {
const { currentState } = FusionStore.store.getState().app;
return currentState.includes(state) !== enforcedState;
};
const checkElementPublish = async ({ parent, query }) => {
const element = parent || document.querySelector(query);
if (element.untilPublished instanceof Promise) {
await element.untilPublished;
}
};
const shouldToggle = (state, enforcedState) => enforcedState === null || shouldAdjustState(state, enforcedState);
const triggerPublishedEventRecursively = (element) => {
// const children = element.querySelectorAll(':not([slot="mo-system"])');
if (element instanceof FusionBase) {
element.emitCustomEvent('published');
}
[...element.children].forEach(el => triggerPublishedEventRecursively(el));
};
/* Private methods end */
class FusionApi {
// @todo: need remove and update editor
static get isEditMode() {
return FusionStore.isEditMode;
}
static getRootNode() {
return document.querySelector(rootSelector);
}
/**
* Add Fusion element to slide or email.
* @private
* @static
* @param name
* @param properties
* @param template
* @param parent
* @returns {any}
*/
static addElement(name, properties, template, parent) {
const element = document.createElement(name);
element.setAttribute('id', this.generateId());
this.setAttributes({ properties, element });
setDefaultTemplate({ template, element });
parent.appendChild(element);
return element;
}
/**
* Private method for save API. Use only in class
* @private
* @static
* */
static async saveElement(element, query, parent, options) {
return this.sendSaveRequest('saveAddedComponent', {
element,
query: query || parent.id,
options,
});
}
/**
+ * Private method for update content API. Use only in class
+ * @static
+ * @param data.query {string} Query for save element
+ * @param data.element {HTMLElement} Element that will be saved
+ * @param data.options {object} Additional params for saving
+ * */
static async updateContent(data) {
await checkElementPublish({ query: data.query });
return this.sendSaveRequest('updateContent', data);
}
/** Private method for send request to Activator from Fusion part
* @private
* @static
* @param name
* @param options
*/
static sendSaveRequest(name, options) {
let requestObj = null;
const map = {
saveAddedComponent: generateAddedElementData,
saveRemovedComponent: generateRemovedElementData,
saveAttributes: generateSaveAttributesData,
saveStyles: generateSaveStylesData,
updateContent: generateUpdateContentData,
};
if (Object.prototype.hasOwnProperty.call(map, name)) {
requestObj = map[name](options);
requestObj.data.ignoreUndo = options.ignoreUndo;
this.request(requestObj);
} else {
throw new Error(`Unknown save request name ${name}`);
}
}
/** Private method to delete actions, connected to the removed element
* @private
* @static
* @param {string} id
* @returns {boolean}
*/
static checkActionsDeletion(id) {
const selectorAttrs = ['on', 'to'];
const relatedActionsSelector = selectorAttrs
.map(attr => `fusion-action[${attr}*="#${id}"]`).join(', ');
const actionElements = [...document.querySelectorAll(relatedActionsSelector)];
actionElements.forEach(el => this.deleteElement(el.id, false, true));
return actionElements.length > 0;
}
// Are we in iRep, Accelerator, Activator, or Fusion Presentation?
static get env() {
return false;
}
static get isTouchSupported() {
return 'ontouchstart' in window;
}
static get subscribe() {
return FusionStore.subscribe;
}
static getEventsPreset() {
return this.isTouchSupported ? {
upEvent: 'touchend',
startEvent: 'touchstart',
moveEvent: 'touchmove',
} : {
upEvent: 'mouseup',
startEvent: 'mousedown',
moveEvent: 'mousemove',
};
}
static goTo(slide, presentation, direction, isDocId) {
const navigationData = FusionNavigation.goTo(slide, presentation, direction, isDocId);
if (FusionNavigation.isActivatorGoTo(FusionStore.isActivator, FusionStore.isEditMode)) {
this.request(navigationData);
}
}
static trackCustomData(data) {
monitoring.trackCustomData(data);
}
static async getThemeConfig() {
return themeApplier.getConfig();
}
/**
* @description receiving array of fragments names from fragment folders
* @returns {Array<String>}
*/
static getFragments() {
// @todo: not the best solution, since they are included into main.js
const customContext = require.context('../../fragments/', true, /\.html$/);
return customContext.keys().reduce((array, key) => {
// Expected to have folder with index.html and thumb.png (not required) files;
const fragmentName = key.split('/')[1];
array.push(fragmentName);
return array;
}, []);
}
/**
* @param {{ name: String, data: Object }} data
*/
static request(data) {
return editorMessenger.request(data);
}
/**
*
* @param name - Component name
* @param properties - Component static props
* @param template - Template Component static template
* @param parent - Parent element where we will append element
* @param query - csspath for parent element
* @param options - pack of params for behaviour
* @returns {HTMLElement}
*/
static async createElement(name, properties = {}, template, parent, query, options = { setActive: true, setState: true }) {
if (!name) throw new Error('[ERR:Components:UI] Unknown name');
await checkElementPublish({ parent });
const element = await this.addElement(name, properties, template, parent);
await this.saveElement(element, query, parent, options);
triggerPublishedEventRecursively(element);
return element;
}
static createActivatorAttrs(element, attrs) {
element.setAttribute(attrs.category, attrs.categories.component);
element.setAttribute(attrs.type, attrs.categories.component);
}
static deleteElement(id, deselectedElement, ignoreUndo) {
this.checkActionsDeletion(id);
const element = document.getElementById(id);
let parent = null;
if (element) {
const state = getComponentState(element);
parent = element.parentNode;
// @todo: would be better to make this method async, but in that case we will break stuff in the editor
checkElementPublish({ parent }).then(() => {
parent.removeChild(element);
this.sendSaveRequest('saveRemovedComponent', {
state,
element,
ignoreUndo,
deselectedElement,
query: `#${id}`,
});
});
} else {
throw new Error(`[ERR:deleteElement:Fusion] Could not find element with ID ${id}`);
}
return parent;
}
/**
* @description check DOM element by received query selector
* @param {String} query - query selector of DOM element
*/
static async checkElement(query) {
const element = document.querySelector(query);
if (element) {
await checkElementPublish({ parent: element.parentNode });
} else {
FusionLogger.warn(`Element ${query} doesn't exist in DOM`, 'api');
}
return element;
}
static async saveAttributes(query, attributes, ignoreUndo = true) {
return await this.checkElement(query) && this.sendSaveRequest('saveAttributes', { query, attributes, ignoreUndo });
}
static async saveStyles(query, styles, ignoreUndo = true) {
return await this.checkElement(query) && this.sendSaveRequest('saveStyles', { query, styles, ignoreUndo });
}
static getCurrentState() {
return FusionStore.store.getState().app.currentState;
}
static getRegisteredStates() {
return FusionStore.store.getState().app.registeredStates;
}
static getRegisteredComponents() {
return FusionStore.store.getState().app.registeredComponents;
}
/**
* @param {Object} data - configuration for elements align
* @param {String} data.query - parent element selector
* @param {Align} data.align
*/
static alignInnerElements(data) {
const itemsToSave = FusionAligner.applyAlignToItems(data);
if (itemsToSave.length) {
itemsToSave.forEach(({ id, styles }) => {
this.saveStyles(`#${id}`, styles);
});
} else {
FusionLogger.warn('Nothing to align', 'FusionApi');
}
}
static freeze() {
return FusionStore.store.dispatch(freeze());
}
static unfreeze() {
return FusionStore.store.dispatch(unfreeze());
}
static setActivatorEnv() {
editorMessenger.initPublishing();
return FusionStore.store.dispatch(setActivatorEnv());
}
static generateId() {
return (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase();
}
static toggleState(state, enforcedState = null) {
if (shouldToggle(state, enforcedState)) {
FusionStore.store.dispatch(toggleState(state));
document.body.classList.toggle(state);
}
}
static updateLevelById(id) {
const el = document.getElementById(id);
if (el && el.removeLevel) {
el.removeLevel();
el.addLevel();
}
}
/**
* Attributes option object.
* @typedef {Object} UpdateAttrOptions
* @property {Boolean} isComponent - value of attribute
* @property {String} selectorId - id of DOM element
* @property {AttrConfig[]} attrList
*/
/**
* @param {UpdateAttrOptions} data
*/
static updateAttributeList(data) {
return this.request({
name: 'actions/updateAttributeList',
data,
});
}
static getMoData() {
return this.request({
name: 'actions/getMoData',
});
}
/**
*
* @param {object} properties of component
* @param {HTMLElement} component
*/
static setAttributes({ properties = {}, element }) {
Object
.keys(properties)
.forEach((propertyName) => {
const { value } = properties[propertyName];
value !== false
? element.setAttribute(propertyName, value)
: element.removeAttribute(propertyName);
});
}
}
export { FusionApi };