@print-one/grapesjs
Version:
Free and Open Source Web Builder Framework
283 lines (236 loc) • 7.09 kB
text/typescript
import { forEach, isEmpty, isNumber, isString, result } from 'underscore';
import CanvasModule from '..';
import { ModuleModel } from '../../abstract';
import { BoxRect } from '../../common';
import ComponentWrapper from '../../dom_components/model/ComponentWrapper';
import Page from '../../pages/model/Page';
import { createId, isComponent, isObject } from '../../utils/mixins';
import FrameView from '../view/FrameView';
import Frames from './Frames';
const keyAutoW = '__aw';
const keyAutoH = '__ah';
const getDimension = (frame: Frame, type: 'width' | 'height') => {
const dim = frame.get(type);
const viewDim = frame.view?.el[type === 'width' ? 'offsetWidth' : 'offsetHeight'];
if (isNumber(dim)) {
return dim;
} else if (isString(dim) && dim.endsWith('px')) {
return parseFloat(dim);
} else if (viewDim) {
return viewDim;
} else {
return 0;
}
};
/**
* @property {Object|String} component Wrapper component definition. You can also pass an HTML string as components of the default wrapper component.
* @property {String} [width=''] Width of the frame. By default, the canvas width will be taken.
* @property {String} [height=''] Height of the frame. By default, the canvas height will be taken.
* @property {Number} [x=0] Horizontal position of the frame in the canvas.
* @property {Number} [y=0] Vertical position of the frame in the canvas.
*
*/
export default class Frame extends ModuleModel<CanvasModule> {
defaults() {
return {
x: 0,
y: 0,
changesCount: 0,
attributes: {},
width: null,
height: null,
head: [],
component: '',
styles: '',
refFrame: null,
_undo: true,
_undoexc: ['changesCount'],
};
}
view?: FrameView;
/**
* @hideconstructor
*/
constructor(module: CanvasModule, attr: any) {
super(module, attr);
const { em } = this;
const { styles, component } = this.attributes;
const domc = em.Components;
const conf = domc.getConfig();
const allRules = em.Css.getAll();
const idMap: any = {};
const modOpts = { em, config: conf, frame: this, idMap };
if (!isComponent(component)) {
const wrp = isObject(component) ? component : { components: component };
!wrp.type && (wrp.type = 'wrapper');
const Wrapper = domc.getType('wrapper')!.model;
this.set('component', new Wrapper(wrp, modOpts));
}
if (!styles) {
this.set('styles', allRules);
} else if (!isObject(styles)) {
// Avoid losing styles on remapped components
const idMapKeys = Object.keys(idMap);
if (idMapKeys.length && Array.isArray(styles)) {
styles.forEach(style => {
const sel = style.selectors;
if (sel && sel.length == 1) {
const sSel = sel[0];
const idSel = sSel.name && sSel.type === 2 && sSel;
if (idSel && idMap[idSel.name]) {
idSel.name = idMap[idSel.name];
} else if (isString(sSel) && sSel[0] === '#') {
const prevId = sSel.substring(1);
if (prevId && idMap[prevId]) {
sel[0] = `#${idMap[prevId]}`;
}
}
}
});
}
allRules.add(styles);
this.set('styles', allRules);
}
!attr.width && this.set(keyAutoW, 1);
!attr.height && this.set(keyAutoH, 1);
!this.id && this.set('id', createId());
}
get width() {
return getDimension(this, 'width');
}
get height() {
return getDimension(this, 'height');
}
get head(): { tag: string; attributes: any }[] {
return this.get('head');
}
get refFrame(): Frame | undefined {
return this.get('refFrame');
}
get root() {
const { refFrame } = this;
return refFrame?.getComponent() || this.getComponent();
}
initRefs() {
const { refFrame } = this;
if (isString(refFrame)) {
const frame = this.module.framesById[refFrame];
frame && this.set({ refFrame: frame }, { silent: true });
}
}
getBoxRect(): BoxRect {
const { x, y } = this.attributes;
const { width, height } = this;
return {
x,
y,
width,
height,
};
}
onRemove() {
!this.refFrame && this.getComponent().remove({ root: 1 });
}
changesUp(opt: any = {}) {
if (opt.temporary || opt.noCount || opt.avoidStore) {
return;
}
this.set('changesCount', this.get('changesCount') + 1);
}
getComponent(): ComponentWrapper {
return this.get('component');
}
getStyles() {
return this.get('styles');
}
disable() {
this.trigger('disable');
}
remove() {
this.view?.remove();
this.view = undefined;
const coll = this.collection;
return coll && coll.remove(this);
}
getHead() {
return [...this.head];
}
setHead(value: { tag: string; attributes: any }[]) {
return this.set('head', [...value]);
}
addHeadItem(item: { tag: string; attributes: any }) {
this.head.push(item);
}
getHeadByAttr(attr: string, value: any, tag: string) {
return this.head.filter(item => item.attributes && item.attributes[attr] == value && (!tag || tag === item.tag))[0];
}
removeHeadByAttr(attr: string, value: any, tag: string) {
const item = this.getHeadByAttr(attr, value, tag);
const index = this.head.indexOf(item);
if (index >= 0) {
this.head.splice(index, 1);
}
}
addLink(href: string) {
const tag = 'link';
!this.getHeadByAttr('href', href, tag) &&
this.addHeadItem({
tag,
attributes: {
href,
rel: 'stylesheet',
},
});
}
removeLink(href: string) {
this.removeHeadByAttr('href', href, 'link');
}
addScript(src: string) {
const tag = 'script';
!this.getHeadByAttr('src', src, tag) &&
this.addHeadItem({
tag,
attributes: { src },
});
}
removeScript(src: string) {
this.removeHeadByAttr('src', src, 'script');
}
getPage(): Page | undefined {
return (this.collection as unknown as Frames)?.page;
}
_emitUpdated(data = {}) {
this.em.trigger('frame:updated', { frame: this, ...data });
}
hasAutoHeight() {
const { height } = this.attributes;
if (height === 'auto' || this.config.infiniteCanvas) {
return true;
}
return false;
}
toJSON(opts: any = {}) {
const obj = ModuleModel.prototype.toJSON.call(this, opts);
const defaults = result(this, 'defaults');
if (opts.fromUndo) delete obj.component;
delete obj.styles;
delete obj.changesCount;
obj[keyAutoW] && delete obj.width;
obj[keyAutoH] && delete obj.height;
if (obj.refFrame) {
obj.refFrame = obj.refFrame.id;
delete obj.component;
}
// Remove private keys
forEach(obj, (value, key) => {
key.indexOf('_') === 0 && delete obj[key];
});
forEach(defaults, (value, key) => {
if (obj[key] === value) delete obj[key];
});
forEach(['attributes', 'head'], prop => {
if (isEmpty(obj[prop])) delete obj[prop];
});
return obj;
}
}