Free and Open source Web Builder Framework. Next generation tool for building templates without coding
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1097 lines
29 KiB

import { isUndefined, isArray, contains, toArray, keys, bindAll } from 'underscore';
import Backbone from 'backbone';
import $ from '../../utils/cash-dom';
import Extender from '../../utils/extender';
import { getModel, hasWin, isEmptyObj } from '../../utils/mixins';
import { AddOptions, Model } from '../../common';
import Selected from './Selected';
import FrameView from '../../canvas/view/FrameView';
import Editor from '..';
import EditorView from '../view/EditorView';
import { ILoadableModule, IModule, IStorableModule } from '../../abstract/Module';
import CanvasModule from '../../canvas';
import ComponentManager from '../../dom_components';
import CssComposer from '../../css_composer';
import { EditorConfig, EditorConfigKeys } from '../config/config';
import Component from '../../dom_components/model/Component';
import BlockManager from '../../block_manager';
import SelectorManager from '../../selector_manager';
import ParserModule from '../../parser';
import StorageManager from '../../storage_manager';
import TraitManager from '../../trait_manager';
import LayerManager from '../../navigator';
import AssetManager from '../../asset_manager';
import DeviceManager from '../../device_manager';
import PageManager from '../../pages';
import I18nModule from '../../i18n';
import UtilsModule from '../../utils';
import KeymapsModule from '../../keymaps';
import ModalModule from '../../modal_dialog';
import PanelManager from '../../panels';
import CodeManagerModule from '../../code_manager';
import UndoManagerModule from '../../undo_manager';
import RichTextEditorModule from '../../rich_text_editor';
import CommandsModule from '../../commands';
import StyleManager from '../../style_manager';
import CssRule from '../../css_composer/model/CssRule';
import { HTMLGeneratorBuildOptions } from '../../code_manager/model/HtmlGenerator';
import { CssGeneratorBuildOptions } from '../../code_manager/model/CssGenerator';
import ComponentView from '../../dom_components/view/ComponentView';
import { ProjectData } from '../../storage_manager/model/IStorage';
import CssRules from '../../css_composer/model/CssRules';
import Frame from '../../canvas/model/Frame';
import { ComponentAdd, DragMode } from '../../dom_components/model/types';
import ComponentWrapper from '../../dom_components/model/ComponentWrapper';
import { CanvasSpotBuiltInTypes } from '../../canvas/model/CanvasSpot';
Backbone.$ = $;
const deps: (new (em: EditorModel) => IModule)[] = [
UtilsModule,
I18nModule,
KeymapsModule,
UndoManagerModule,
StorageManager,
DeviceManager,
ParserModule,
StyleManager,
SelectorManager,
ModalModule,
CodeManagerModule,
PanelManager,
RichTextEditorModule,
TraitManager,
LayerManager,
CanvasModule,
CommandsModule,
BlockManager,
];
const storableDeps: (new (em: EditorModel) => IModule & IStorableModule)[] = [
AssetManager,
CssComposer,
PageManager,
ComponentManager,
];
Extender({ $ });
const logs = {
debug: console.log,
info: console.info,
warning: console.warn,
error: console.error,
};
export default class EditorModel extends Model {
defaults() {
return {
editing: 0,
selected: 0,
clipboard: null,
dmode: 0,
componentHovered: null,
previousModel: null,
changesCount: 0,
storables: [],
modules: [],
toLoad: [],
opened: {},
device: '',
};
}
__skip = false;
defaultRunning = false;
destroyed = false;
_config: EditorConfig;
_storageTimeout?: ReturnType<typeof setTimeout>;
attrsOrig: any;
timedInterval?: ReturnType<typeof setTimeout>;
updateItr?: ReturnType<typeof setTimeout>;
view?: EditorView;
get storables(): IStorableModule[] {
return this.get('storables');
}
get modules(): IModule[] {
return this.get('modules');
}
get toLoad(): ILoadableModule[] {
return this.get('toLoad');
}
get selected(): Selected {
return this.get('selected');
}
get shallow(): EditorModel {
return this.get('shallow');
}
get I18n(): I18nModule {
return this.get('I18n');
}
get Utils(): UtilsModule {
return this.get('Utils');
}
get Commands(): CommandsModule {
return this.get('Commands');
}
get Keymaps(): KeymapsModule {
return this.get('Keymaps');
}
get Modal(): ModalModule {
return this.get('Modal');
}
get Panels(): PanelManager {
return this.get('Panels');
}
get CodeManager(): CodeManagerModule {
return this.get('CodeManager');
}
get UndoManager(): UndoManagerModule {
return this.get('UndoManager');
}
get RichTextEditor(): RichTextEditorModule {
return this.get('RichTextEditor');
}
get Canvas(): CanvasModule {
return this.get('Canvas');
}
get Editor(): Editor {
return this.get('Editor');
}
get Components(): ComponentManager {
return this.get('DomComponents');
}
get Css(): CssComposer {
return this.get('CssComposer');
}
get Blocks(): BlockManager {
return this.get('BlockManager');
}
get Selectors(): SelectorManager {
return this.get('SelectorManager');
}
get Storage(): StorageManager {
return this.get('StorageManager');
}
get Traits(): TraitManager {
return this.get('TraitManager');
}
get Parser(): ParserModule {
return this.get('Parser');
}
get Layers(): LayerManager {
return this.get('LayerManager');
}
get Assets(): AssetManager {
return this.get('AssetManager');
}
get Devices(): DeviceManager {
return this.get('DeviceManager');
}
get Pages(): PageManager {
return this.get('PageManager');
}
get Styles(): StyleManager {
return this.get('StyleManager');
}
constructor(conf: EditorConfig = {}) {
super();
this._config = conf;
const { config } = this;
this.set('Config', conf);
this.set('modules', []);
this.set('toLoad', []);
this.set('storables', []);
this.set('selected', new Selected());
this.set('dmode', config.dragMode);
const { el, log } = config;
const toLog = log === true ? keys(logs) : isArray(log) ? log : [];
bindAll(this, 'initBaseColorPicker');
if (el && config.fromElement) {
config.components = el.innerHTML;
}
this.attrsOrig = el
? toArray(el.attributes).reduce((res, next) => {
res[next.nodeName] = next.nodeValue;
return res;
}, {} as Record<string, any>)
: '';
// Move components to pages
if (config.components && !config.pageManager) {
config.pageManager = { pages: [{ component: config.components }] };
}
// Load modules
deps.forEach(constr => this.loadModule(constr));
storableDeps.forEach(constr => this.loadStorableModule(constr));
this.on('change:componentHovered', this.componentHovered, this);
this.on('change:changesCount', this.updateChanges, this);
this.on('change:readyLoad change:readyCanvas', this._checkReady, this);
toLog.forEach(e => this.listenLog(e));
// Deprecations
[{ from: 'change:selectedComponent', to: 'component:toggled' }].forEach(event => {
const eventFrom = event.from;
const eventTo = event.to;
this.listenTo(this, eventFrom, (...args) => {
this.trigger(eventTo, ...args);
this.logWarning(`The event '${eventFrom}' is deprecated, replace it with '${eventTo}'`);
});
});
}
_checkReady() {
if (this.get('readyLoad') && this.get('readyCanvas') && !this.get('ready')) {
this.set('ready', true);
}
}
getContainer() {
return this.config.el;
}
listenLog(event: string) {
//@ts-ignore
this.listenTo(this, `log:${event}`, logs[event]);
}
get config() {
return this._config;
}
/**
* Get configurations
* @param {string} [prop] Property name
* @return {any} Returns the configuration object or
* the value of the specified property
*/
getConfig<
P extends EditorConfigKeys | undefined = undefined,
R = P extends EditorConfigKeys ? EditorConfig[P] : EditorConfig
>(prop?: P): R {
const { config } = this;
// @ts-ignore
return isUndefined(prop) ? config : config[prop];
}
/**
* Should be called once all modules and plugins are loaded
* @private
*/
loadOnStart() {
const { projectData, headless } = this.config;
const sm = this.get('StorageManager');
// In `onLoad`, the module will try to load the data from its configurations.
this.toLoad.reverse().forEach(mdl => mdl.onLoad());
// Stuff to do post load
const postLoad = () => {
this.modules.forEach(mdl => mdl.postLoad && mdl.postLoad(this));
this.set('readyLoad', 1);
};
if (headless) {
projectData && this.loadData(projectData);
postLoad();
} else {
// Defer for storage load events.
this._storageTimeout = setTimeout(async () => {
if (projectData) {
this.loadData(projectData);
} else if (sm?.canAutoload()) {
try {
await this.load();
} catch (error) {
this.logError(error as string);
}
}
postLoad();
});
}
// Create shallow editor.
// Here we can create components/styles without altering/triggering the main EditorModel
const shallow = new EditorModel({
noticeOnUnload: false,
storageManager: false,
undoManager: false,
});
// We only need to load a few modules
shallow.Pages.onLoad();
shallow.Canvas.postLoad();
this.set('shallow', shallow);
}
/**
* Set the alert before unload in case it's requested
* and there are unsaved changes
* @private
*/
updateChanges() {
const stm = this.get('StorageManager');
const changes = this.getDirtyCount();
this.updateItr && clearTimeout(this.updateItr);
this.updateItr = setTimeout(() => this.trigger('update'));
if (this.config.noticeOnUnload) {
window.onbeforeunload = changes ? () => true : null;
}
if (stm.isAutosave() && changes >= stm.getStepsBeforeSave()) {
this.store().catch(err => this.logError(err));
}
}
/**
* Load generic module
*/
private loadModule(InitModule: new (em: EditorModel) => IModule) {
const Mod = new InitModule(this);
this.set(Mod.name, Mod);
Mod.onLoad && this.toLoad.push(Mod as ILoadableModule);
this.modules.push(Mod);
return Mod;
}
private loadStorableModule(InitModule: new (em: EditorModel) => IModule & IStorableModule) {
const Mod = this.loadModule(InitModule) as IModule & IStorableModule;
this.storables.push(Mod);
return Mod;
}
/**
* Initialize editor model and set editor instance
* @param {Editor} editor Editor instance
* @return {this}
* @public
*/
init(editor: Editor, opts = {}) {
if (this.destroyed) {
this.initialize(opts);
this.destroyed = false;
}
this.set('Editor', editor);
}
getEditor(): Editor {
return this.get('Editor');
}
/**
* This method handles updates on the editor and tries to store them
* if requested and if the changesCount is exceeded
* @param {Object} model
* @param {any} val Value
* @param {Object} opt Options
* @private
* */
handleUpdates(model: any, val: any, opt: any = {}) {
// Component has been added temporarily - do not update storage or record changes
if (this.__skip || opt.temporary || opt.noCount || opt.avoidStore || !this.get('ready')) {
return;
}
this.timedInterval && clearTimeout(this.timedInterval);
this.timedInterval = setTimeout(() => {
const curr = this.getDirtyCount() || 0;
const { unset, ...opts } = opt;
this.set('changesCount', curr + 1, opts);
}, 0);
}
changesUp(opts: any) {
this.handleUpdates(0, 0, opts);
}
/**
* Callback on component hover
* @param {Object} Model
* @param {Mixed} New value
* @param {Object} Options
* @private
* */
componentHovered(editor: any, component: any, options: any) {
const prev = this.previous('componentHovered');
prev && this.trigger('component:unhovered', prev, options);
component && this.trigger('component:hovered', component, options);
}
/**
* Returns model of the selected component
* @return {Component|null}
* @public
*/
getSelected() {
return this.selected.lastComponent();
}
/**
* Returns an array of all selected components
* @return {Array}
* @public
*/
getSelectedAll() {
return this.selected.allComponents();
}
/**
* Select a component
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @public
*/
setSelected(el?: Component | Component[], opts: any = {}) {
const { event } = opts;
const ctrlKey = event && (event.ctrlKey || event.metaKey);
const { shiftKey } = event || {};
const els = (isArray(el) ? el : [el]).map(el => getModel(el, $));
const selected = this.getSelectedAll();
const mltSel = this.getConfig().multipleSelection;
let added;
// If an array is passed remove all selected
// expect those yet to be selected
const multiple = isArray(el);
multiple && this.removeSelected(selected.filter(s => !contains(els, s)));
els.forEach(el => {
let model = getModel(el, undefined);
if (model) {
this.trigger('component:select:before', model, opts);
// Check for valid selectable
if (!model.get('selectable') || opts.abort) {
if (opts.useValid) {
let parent = model.parent();
while (parent && !parent.get('selectable')) parent = parent.parent();
model = parent;
} else {
return;
}
}
}
// Hanlde multiple selection
if (ctrlKey && mltSel) {
return this.toggleSelected(model);
} else if (shiftKey && mltSel) {
this.clearSelection(this.get('Canvas').getWindow());
const coll = model.collection;
const index = model.index();
let min: number | undefined, max: number | undefined;
// Fin min and max siblings
this.getSelectedAll().forEach(sel => {
const selColl = sel.collection;
const selIndex = sel.index();
if (selColl === coll) {
if (selIndex < index) {
// First model BEFORE the selected one
min = isUndefined(min) ? selIndex : Math.max(min, selIndex);
} else if (selIndex > index) {
// First model AFTER the selected one
max = isUndefined(max) ? selIndex : Math.min(max, selIndex);
}
}
});
if (!isUndefined(min)) {
while (min !== index) {
this.addSelected(coll.at(min));
min++;
}
}
if (!isUndefined(max)) {
while (max !== index) {
this.addSelected(coll.at(max));
max--;
}
}
return this.addSelected(model);
}
!multiple && this.removeSelected(selected.filter(s => s !== model));
this.addSelected(model, opts);
added = model;
});
}
/**
* Add component to selection
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @public
*/
addSelected(el: Component | Component[], opts: any = {}) {
const model = getModel(el, $);
const models: Component[] = isArray(model) ? model : [model];
models.forEach(model => {
const { selected } = this;
if (
!model ||
!model.get('selectable') ||
// Avoid selecting children of selected components
model.parents().some((parent: Component) => selected.hasComponent(parent))
) {
return;
}
opts.forceChange && this.removeSelected(model, opts);
// Remove from selection, children of the component to select
const toDeselect = selected.allComponents().filter(cmp => contains(cmp.parents(), model));
toDeselect.forEach(cmp => this.removeSelected(cmp, opts));
selected.addComponent(model, opts);
this.trigger('component:select', model, opts);
this.Canvas.setSpot({
type: CanvasSpotBuiltInTypes.Select,
component: model,
});
});
}
/**
* Remove component from selection
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @public
*/
removeSelected(el: Component | Component[], opts = {}) {
const component = getModel(el, $);
this.selected.removeComponent(component, opts);
const cmps: Component[] = isArray(component) ? component : [component];
cmps.forEach(component =>
this.Canvas.removeSpot({
type: CanvasSpotBuiltInTypes.Select,
component,
})
);
}
/**
* Toggle component selection
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @public
*/
toggleSelected(el: Component | Component[], opts: any = {}) {
const model = getModel(el, $);
const models = isArray(model) ? model : [model];
models.forEach(model => {
if (this.selected.hasComponent(model)) {
this.removeSelected(model, opts);
} else {
this.addSelected(model, opts);
}
});
}
/**
* Hover a component
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @private
*/
setHovered(el: any, opts: any = {}) {
if (!el) return this.set('componentHovered', '');
const ev = 'component:hover';
let model = getModel(el, undefined);
if (!model) return;
opts.forceChange && this.set('componentHovered', '');
this.trigger(`${ev}:before`, model, opts);
// Check for valid hoverable
if (!model.get('hoverable')) {
if (opts.useValid && !opts.abort) {
let parent = model && model.parent();
while (parent && !parent.get('hoverable')) parent = parent.parent();
model = parent;
} else {
return;
}
}
if (!opts.abort) {
this.set('componentHovered', model, opts);
this.trigger(ev, model, opts);
}
}
getHovered() {
return this.get('componentHovered');
}
/**
* Set components inside editor's canvas. This method overrides actual components
* @param {Object|string} components HTML string or components model
* @param {Object} opt the options object to be used by the [setComponents]{@link setComponents} method
* @return {this}
* @public
*/
setComponents(components: ComponentAdd, opt: AddOptions = {}) {
return this.Components.setComponents(components, opt);
}
/**
* Returns components model from the editor's canvas
* @return {Components}
* @private
*/
getComponents() {
const cmp = this.Components;
const cm = this.CodeManager;
if (!cmp || !cm) return;
const wrp = cmp.getComponents();
return cm.getCode(wrp, 'json');
}
/**
* Set style inside editor's canvas. This method overrides actual style
* @param {Object|string} style CSS string or style model
* @param {Object} opt the options object to be used by the `CssRules.add` method
* @return {this}
* @public
*/
setStyle(style: any, opt = {}) {
const cssc = this.Css;
cssc.clear(opt);
cssc.getAll().add(style, opt);
return this;
}
/**
* Add styles to the editor
* @param {Array<Object>|Object|string} style CSS string or style model
* @returns {Array<CssRule>}
* @public
*/
addStyle(style: any, opts = {}): CssRule[] {
const res = this.getStyle().add(style, opts);
return isArray(res) ? res : [res];
}
/**
* Returns rules/style model from the editor's canvas
* @return {Rules}
* @private
*/
getStyle(): CssRules {
return this.Css.getAll();
}
/**
* Change the selector state
* @param {String} value State value
* @returns {this}
*/
setState(value: string) {
this.set('state', value);
return this;
}
/**
* Get the current selector state
* @returns {String}
*/
getState(): string {
return this.get('state') || '';
}
/**
* Returns HTML built inside canvas
* @param {Object} [opts={}] Options
* @returns {string} HTML string
* @public
*/
getHtml(opts: { component?: Component } & HTMLGeneratorBuildOptions = {}): string {
const { config } = this;
const { optsHtml } = config;
const js = config.jsInHtml ? this.getJs(opts) : '';
const cmp = opts.component || this.Components.getComponent();
let html = cmp
? this.CodeManager.getCode(cmp, 'html', {
...optsHtml,
...opts,
})
: '';
html += js ? `<script>${js}</script>` : '';
return html;
}
/**
* Returns CSS built inside canvas
* @param {Object} [opts={}] Options
* @returns {string} CSS string
* @public
*/
getCss(opts: { component?: Component; avoidProtected?: boolean } & CssGeneratorBuildOptions = {}) {
const { config } = this;
const { optsCss } = config;
const avoidProt = opts.avoidProtected;
const keepUnusedStyles = !isUndefined(opts.keepUnusedStyles) ? opts.keepUnusedStyles : config.keepUnusedStyles;
const cssc = this.Css;
const wrp = opts.component || this.Components.getComponent();
const protCss = !avoidProt ? config.protectedCss! : '';
const css =
wrp &&
this.CodeManager.getCode(wrp, 'css', {
cssc,
keepUnusedStyles,
...optsCss,
...opts,
});
return wrp ? (opts.json ? css : protCss + css) : '';
}
/**
* Returns JS of all components
* @return {string} JS string
* @public
*/
getJs(opts: { component?: Component } = {}) {
var wrp = opts.component || this.Components.getWrapper();
return wrp ? this.CodeManager.getCode(wrp, 'js').trim() : '';
}
/**
* Store data to the current storage.
* @public
*/
async store(options?: any) {
const data = this.storeData();
await this.Storage.store(data, options);
this.clearDirtyCount();
return data;
}
/**
* Load data from the current storage.
* @public
*/
async load(options?: any) {
const result = await this.Storage.load(options);
this.loadData(result);
return result;
}
storeData(): ProjectData {
let result = {};
// Sync content if there is an active RTE
const editingCmp = this.getEditing();
editingCmp && editingCmp.trigger('sync:content', { noCount: true });
this.storables.forEach(m => {
result = { ...result, ...m.store(1) };
});
return JSON.parse(JSON.stringify(result));
}
loadData(data: ProjectData = {}): ProjectData {
if (!isEmptyObj(data)) {
this.storables.forEach(module => module.clear());
this.storables.forEach(module => module.load(data));
}
return data;
}
/**
* Returns device model by name
* @return {Device|null}
* @private
*/
getDeviceModel() {
const name = this.get('device');
return this.Devices.get(name);
}
/**
* Run default command if setted
* @param {Object} [opts={}] Options
* @private
*/
runDefault(opts = {}) {
const command = this.get('Commands').get(this.config.defaultCommand);
if (!command || this.defaultRunning) return;
command.stop(this, this, opts);
command.run(this, this, opts);
this.defaultRunning = true;
}
/**
* Stop default command
* @param {Object} [opts={}] Options
* @private
*/
stopDefault(opts = {}) {
const commands = this.get('Commands');
const command = commands.get(this.config.defaultCommand);
if (!command || !this.defaultRunning) return;
command.stop(this, this, opts);
this.defaultRunning = false;
}
/**
* Update canvas dimensions and refresh data useful for tools positioning
* @public
*/
refreshCanvas(opts: any = {}) {
this.set('canvasOffset', null);
this.set('canvasOffset', this.Canvas.getOffset());
opts.tools && this.trigger('canvas:updateTools');
}
/**
* Clear all selected stuf inside the window, sometimes is useful to call before
* doing some dragging opearation
* @param {Window} win If not passed the current one will be used
* @private
*/
clearSelection(win?: Window) {
var w = win || window;
w.getSelection()?.removeAllRanges();
}
/**
* Get the current media text
* @return {string}
*/
getCurrentMedia() {
const config = this.config;
const device = this.getDeviceModel();
const condition = config.mediaCondition;
const preview = config.devicePreviewMode;
const width = device && device.get('widthMedia');
return device && width && !preview ? `(${condition}: ${width})` : '';
}
/**
* Return the component wrapper
* @return {Component}
*/
getWrapper(): ComponentWrapper | undefined {
return this.Components.getWrapper();
}
setCurrentFrame(frameView?: FrameView) {
return this.set('currentFrame', frameView);
}
getCurrentFrame(): FrameView | undefined {
return this.get('currentFrame');
}
getCurrentFrameModel() {
return (this.getCurrentFrame() || {})?.model;
}
getIcon(icon: string) {
const icons = this.config.icons || {};
return icons[icon] || '';
}
/**
* Return the count of changes made to the content and not yet stored.
* This count resets at any `store()`
* @return {number}
*/
getDirtyCount(): number {
return this.get('changesCount');
}
clearDirtyCount() {
return this.set('changesCount', 0);
}
getZoomDecimal() {
return this.Canvas.getZoomDecimal();
}
getZoomMultiplier() {
return this.Canvas.getZoomMultiplier();
}
setDragMode(value: DragMode) {
return this.set('dmode', value);
}
getDragMode(component?: Component): DragMode {
const mode = component?.getDragMode() || this.get('dmode');
return mode || '';
}
t(...args: any[]) {
const i18n = this.get('I18n');
return i18n?.t(...args);
}
/**
* Returns true if the editor is in absolute mode
* @returns {Boolean}
*/
inAbsoluteMode(component?: Component) {
return this.getDragMode(component) === 'absolute';
}
/**
* Destroy editor
*/
destroyAll() {
const { config, view } = this;
const editor = this.getEditor();
// @ts-ignore
const { editors = [] } = config.grapesjs || {};
const shallow = this.get('shallow');
this._storageTimeout && clearTimeout(this._storageTimeout);
shallow?.destroyAll();
this.stopListening();
this.stopDefault();
this.modules
.slice()
.reverse()
.forEach(mod => mod.destroy());
view && view.remove();
this.clear({ silent: true });
this.destroyed = true;
['_config', 'view', '_previousAttributes', '_events', '_listeners'].forEach(
//@ts-ignore
i => (this[i] = {})
);
editors.splice(editors.indexOf(editor), 1);
//@ts-ignore
hasWin() && $(config.el).empty().attr(this.attrsOrig);
}
getEditing(): Component | undefined {
const res = this.get('editing');
return (res && res.model) || undefined;
}
setEditing(value: boolean | ComponentView) {
this.set('editing', value);
return this;
}
isEditing() {
return !!this.get('editing');
}
log(msg: string, opts: any = {}) {
const { ns, level = 'debug' } = opts;
this.trigger('log', msg, opts);
level && this.trigger(`log:${level}`, msg, opts);
if (ns) {
const logNs = `log-${ns}`;
this.trigger(logNs, msg, opts);
level && this.trigger(`${logNs}:${level}`, msg, opts);
}
}
logInfo(msg: string, opts?: any) {
this.log(msg, { ...opts, level: 'info' });
}
logWarning(msg: string, opts?: any) {
this.log(msg, { ...opts, level: 'warning' });
}
logError(msg: string, opts?: any) {
this.log(msg, { ...opts, level: 'error' });
}
initBaseColorPicker(el: any, opts = {}) {
const { config } = this;
const { colorPicker = {} } = config;
const elToAppend = config.el;
const ppfx = config.stylePrefix;
//@ts-ignore
return $(el).spectrum({
containerClassName: `${ppfx}one-bg ${ppfx}two-color`,
appendTo: elToAppend || 'body',
maxSelectionSize: 8,
showPalette: true,
palette: [],
showAlpha: true,
chooseText: 'Ok',
cancelText: '⨯',
...opts,
...colorPicker,
});
}
/**
* Execute actions without triggering the storage and undo manager.
* @param {Function} clb
* @private
*/
skip(clb: Function) {
this.__skip = true;
const um = this.UndoManager;
um ? um.skip(clb) : clb();
this.__skip = false;
}
/**
* Set/get data from the HTMLElement
* @param {HTMLElement} el
* @param {string} name Data name
* @param {any} value Date value
* @return {any}
* @private
*/
data(el: any, name: string, value: any) {
const varName = '_gjs-data';
if (!el[varName]) {
el[varName] = {};
}
if (isUndefined(value)) {
return el[varName][name];
} else {
el[varName][name] = value;
}
}
}