Browse Source

Convert pages into Module class

pull/4302/head
Alex 4 years ago
parent
commit
48b33e12bd
  1. 211
      src/abstract/Module.ts
  2. 8
      src/abstract/index.ts
  3. 3
      src/canvas/model/Frames.js
  4. 41
      src/editor/index.ts
  5. 6
      src/editor/model/Editor.ts
  6. 415
      src/pages/index.js
  7. 7
      src/pages/model/Page.ts

211
src/abstract/Module.ts

@ -1,6 +1,10 @@
import { isElement, isUndefined } from "underscore";
import { Collection } from "../common";
import EditorModel from "../editor/model/Editor";
import { createId, isDef } from "../utils/mixins";
export interface IModule<TConfig extends any = any> extends IBaseModule<TConfig> {
export interface IModule<TConfig extends any = any>
extends IBaseModule<TConfig> {
init(cfg: any): void;
destroy(): void;
postLoad(key: any): any;
@ -15,26 +19,41 @@ export interface IBaseModule<TConfig extends any> {
config: TConfig;
}
interface ModuleConfig{
interface ModuleConfig {
name: string;
stylePrefix?: string;
}
export interface IStorableModule extends IModule {
storageKey: string[] | string;
store(result: any): any;
load(keys: string[]): void;
postLoad(key: any): any;
}
export default abstract class Module<T extends ModuleConfig = ModuleConfig>
implements IModule<T>
{
//conf: CollectionCollectionModuleConfig;
private _em: EditorModel;
private _config: T;
private _name: string;
cls: any[] = [];
events: any;
constructor(
em: EditorModel,
config: T
) {
constructor(em: EditorModel, moduleName: string) {
this._em = em;
this._config = config;
this._name = moduleName;
const name = this.name.charAt(0).toLowerCase() + this.name.slice(1);
const cfgParent = !isUndefined(em.config[name])
? em.config[name]
: em.config[this.name];
const cfg = cfgParent === true ? {} : cfgParent || {};
cfg.pStylePrefix = em.config.pStylePrefix || "";
if (!isUndefined(cfgParent) && !cfgParent) {
cfg._disable = 1;
}
this._config = cfg;
}
public get em() {
@ -51,16 +70,186 @@ export default abstract class Module<T extends ModuleConfig = ModuleConfig>
postLoad(key: any): void {}
get name(): string {
return this.config.name;
return this._name;
}
getConfig() {
return this.config;
}
__logWarn(str: string) {
this.em.logWarning(`[${this.name}]: ${str}`);
__logWarn(str: string, opts = {}) {
this.em.logWarning(`[${this.name}]: ${str}`, opts);
}
postRender?(view: any): void;
}
export abstract class ItemManagerModule<
TConf extends ModuleConfig = any,
TModel extends Collection = any
> extends Module<TConf> {
cls: any[] = [];
protected all: TModel;
constructor(em: EditorModel, moduleName: string, all: any, events: any) {
super(em, moduleName);
this.all = all;
this.events = events;
this.__initListen();
}
private: boolean = false;
abstract storageKey: string;
abstract init(cfg: any): void;
abstract destroy(): void;
postLoad(key: any): void {}
abstract postRender(view: any): void;
abstract render(): any;
getProjectData(data: any) {
const obj: any = {};
const key = this.storageKey;
if (key) {
obj[key] = data || this.getAll();
}
return obj;
}
loadProjectData(
data: any = {},
param: { all?: TModel; onResult?: Function; reset?: boolean } = {}
) {
const { all, onResult, reset } = param;
const key = this.storageKey;
const opts: any = { action: "load" };
const coll = all || this.getAll();
let result = data[key];
if (typeof result == "string") {
try {
result = JSON.parse(result);
} catch (err) {
this.__logWarn("Data parsing failed", { input: result });
}
}
reset && result && coll.reset(undefined, opts);
if (onResult) {
result && onResult(result, opts);
} else if (result && isDef(result.length)) {
coll.reset(result, opts);
}
return result;
}
clear(opts = {}) {
const { all } = this;
all && all.reset(undefined, opts);
return this;
}
getAll() {
return this.all;
}
getAllMap() {
return this.getAll().reduce((acc: { [id: string]: TModel }, i: any) => {
acc[i.get(i.idAttribute)] = i;
return acc;
}, {});
}
__initListen(opts: any = {}) {
const { all, em, events } = this;
all &&
em &&
all
.on("add", (m: any, c: any, o: any) => em.trigger(events.add, m, o))
.on("remove", (m: any, c: any, o: any) =>
em.trigger(events.remove, m, o)
)
.on("change", (p: any, c: any) =>
em.trigger(events.update, p, p.changedAttributes(), c)
)
.on("all", this.__catchAllEvent, this);
// Register collections
this.cls = [all].concat(opts.collections || []);
// Propagate events
((opts.propagate as any[]) || []).forEach(({ entity, event }) => {
entity.on("all", (ev: any, model: any, coll: any, opts: any) => {
const options = opts || coll;
const opt = { event: ev, ...options };
[em, all].map((md) => md.trigger(event, model, opt));
});
});
}
__remove(model: any, opts: any = {}) {
const { em } = this;
//@ts-ignore
const md = isString(model) ? this.get(model) : model;
const rm = () => {
md && this.all.remove(md, opts);
return md;
};
!opts.silent && em?.trigger(this.events.removeBefore, md, rm, opts);
return !opts.abort && rm();
}
__catchAllEvent(event: any, model: any, coll: any, opts: any) {
const { em, events } = this;
const options = opts || coll;
em && events.all && em.trigger(events.all, { event, model, options });
this.__onAllEvent();
}
__appendTo() {
//@ts-ignore
const elTo = this.config.appendTo;
if (elTo) {
const el = isElement(elTo) ? elTo : document.querySelector(elTo);
if (!el) return this.__logWarn('"appendTo" element not found');
el.appendChild(this.render());
}
}
__onAllEvent() {}
_createId(len = 16) {
const all = this.getAll();
const ln = all.length + len;
const allMap = this.getAllMap();
let id;
do {
id = createId(ln);
} while (allMap[id]);
return id;
}
__listenAdd(model: TModel, event: string) {
model.on("add", (m, c, o) => this.em.trigger(event, m, o));
}
__listenRemove(model: TModel, event: string) {
model.on("remove", (m, c, o) => this.em.trigger(event, m, o));
}
__listenUpdate(model: TModel, event: string) {
model.on("change", (p, c) =>
this.em.trigger(event, p, p.changedAttributes(), c)
);
}
__destroy() {
this.cls.forEach((coll) => {
coll.stopListening();
coll.reset();
});
}
}

8
src/abstract/index.ts

@ -1,4 +1,4 @@
export { default as Model } from './Model';
export { default as Collection } from './Collection';
export { default as View } from './View';
export { default as Module } from './moduleLegacy';
export { default as Model } from "./Model";
export { default as Collection } from "./Collection";
export { default as View } from "./View";
export { default as Module } from "./Module";

3
src/canvas/model/Frames.js

@ -4,7 +4,8 @@ import { Collection } from '../../common';
import Frame from './Frame';
export default class Frames extends Collection {
initialize(models, config = {}) {
constructor(models, config = {}) {
super(models);
bindAll(this, 'itemLoaded');
this.config = config;
this.on('reset', this.onReset);

41
src/editor/index.ts

@ -54,19 +54,24 @@
* ## Methods
* @module Editor
*/
import { EventHandler } from 'backbone';
import { isUndefined } from 'underscore';
import { IBaseModule } from '../abstract/Module';
import cash from '../utils/cash-dom';
import html from '../utils/html';
import defaults from './config/config';
import EditorModel from './model/Editor';
import EditorView from './view/EditorView';
import { EventHandler } from "backbone";
import { isUndefined } from "underscore";
import { IBaseModule } from "../abstract/Module";
import cash from "../utils/cash-dom";
import html from "../utils/html";
import defaults from "./config/config";
import EditorModel from "./model/Editor";
import EditorView from "./view/EditorView";
export default class EditorModule implements IBaseModule<typeof defaults> {
constructor(config = {}, opts: any = {}) {
//@ts-ignore
this.config = { ...defaults, ...config, pStylePrefix: defaults.stylePrefix };
this.config = {
...defaults,
...config,
//@ts-ignore
pStylePrefix: defaults.stylePrefix,
};
this.em = new EditorModel(this.config);
this.$ = opts.$;
this.em.init(this);
@ -206,7 +211,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
//@ts-ignore
get Devices(): DeviceManagerModule {
return this.em.get("DeviceManager");
}
}
//@ts-ignore
get DeviceManager(): DeviceManagerModule {
return this.em.get("DeviceManager");
@ -259,7 +264,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* @return {Components}
*/
getComponents() {
return this.em.get('DomComponents').getComponents();
return this.em.get("DomComponents").getComponents();
}
/**
@ -267,7 +272,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* @return {Component}
*/
getWrapper() {
return this.em.get('DomComponents').getWrapper();
return this.em.get("DomComponents").getWrapper();
}
/**
@ -315,7 +320,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* @return {Object}
*/
getStyle() {
return this.em.get('CssComposer').getAll();
return this.em.get("CssComposer").getAll();
}
/**
@ -453,7 +458,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* editor.setDevice('Tablet');
*/
setDevice(name: string) {
this.em.set('device', name);
this.em.set("device", name);
return this;
}
@ -466,7 +471,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* // 'Tablet'
*/
getDevice() {
return this.em.get('device');
return this.em.get("device");
}
/**
@ -478,7 +483,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* editor.runCommand('myCommand', {someValue: 1});
*/
runCommand(id: string, options = {}) {
return this.em.get('Commands').run(id, options);
return this.em.get("Commands").run(id, options);
}
/**
@ -490,7 +495,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* editor.stopCommand('myCommand', {someValue: 1});
*/
stopCommand(id: string, options = {}) {
return this.em.get('Commands').stop(id, options);
return this.em.get("Commands").stop(id, options);
}
/**
@ -779,7 +784,7 @@ export default class EditorModule implements IBaseModule<typeof defaults> {
* });
*/
onReady(clb: EventHandler) {
this.em.get('ready') ? clb(this) : this.em.on('load', clb);
this.em.get("ready") ? clb(this) : this.em.on("load", clb);
}
/**

6
src/editor/model/Editor.ts

@ -45,7 +45,9 @@ const deps = [
require("block_manager"),
];
const ts_deps: any[] = [];
const ts_deps: any[] = [
//require("pages")
];
Extender({
//@ts-ignore
@ -264,7 +266,7 @@ export default class EditorModel extends Model {
loadModule(moduleName: any) {
const { config } = this;
const Module = moduleName.default || moduleName;
const Mod = new Module();
const Mod = new Module(this);
const name = Mod.name.charAt(0).toLowerCase() + Mod.name.slice(1);
const cfgParent = !isUndefined(config[name])
? config[name]

415
src/pages/index.js

@ -46,9 +46,11 @@
import { isString, bindAll, unique, flatten } from 'underscore';
import { createId } from '../utils/mixins';
import { Model, Module } from '../common';
import { Model, Module } from '../abstract';
import { ItemManagerModule } from '../abstract/Module';
import Pages from './model/Pages';
import Page from './model/Page';
import EditorModel from '../editor/model/Editor';
export const evAll = 'page';
export const evPfx = `${evAll}:`;
@ -61,239 +63,226 @@ export const evPageRemove = `${evPfx}remove`;
export const evPageRemoveBefore = `${evPageRemove}:before`;
const chnSel = 'change:selected';
const typeMain = 'main';
const events = {
all: evAll,
select: evPageSelect,
selectBefore: evPageSelectBefore,
update: evPageUpdate,
add: evPageAdd,
addBefore: evPageAddBefore,
remove: evPageRemove,
removeBefore: evPageRemoveBefore,
};
export default class PageManager extends ItemManagerModule {
name = 'PageManager';
export default () => {
return {
...Module,
name: 'PageManager',
storageKey: 'pages',
Page,
storageKey = 'pages';
Pages,
Page;
events: {
all: evAll,
select: evPageSelect,
selectBefore: evPageSelectBefore,
update: evPageUpdate,
add: evPageAdd,
addBefore: evPageAddBefore,
remove: evPageRemove,
removeBefore: evPageRemoveBefore,
},
Pages;
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init(opts = {}) {
bindAll(this, '_onPageChange');
const { em } = opts;
const cnf = { ...opts };
this.config = cnf;
this.em = em;
const pages = new Pages([]);
this.pages = pages;
this.all = pages;
const model = new Model({ _undo: true });
this.model = model;
pages.on('add', (p, c, o) => em.trigger(evPageAdd, p, o));
pages.on('remove', (p, c, o) => em.trigger(evPageRemove, p, o));
pages.on('change', (p, c) => {
em.trigger(evPageUpdate, p, p.changedAttributes(), c);
});
pages.on('reset', coll => coll.at(0) && this.select(coll.at(0)));
pages.on('all', this.__onChange, this);
model.on(chnSel, this._onPageChange);
get pages() {
return this.all;
}
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
constructor(em) {
const pages = new Pages([]);
super(em, 'PageManager', pages, events);
bindAll(this, '_onPageChange');
const model = new Model({ _undo: true });
this.model = model;
pages.on('reset', coll => coll.at(0) && this.select(coll.at(0)));
pages.on('all', this.__onChange, this);
model.on(chnSel, this._onPageChange);
return this;
},
return this;
}
__onChange(event, page, coll, opts) {
const options = opts || coll;
this.em.trigger(evAll, { event, page, options });
},
__onChange(event, page, coll, opts) {
const options = opts || coll;
this.em.trigger(evAll, { event, page, options });
}
onLoad() {
const { pages } = this;
const opt = { silent: true };
pages.add(this.config.pages?.map(page => new Page(page, { em: this.em, config: this.config })) || [], opt);
const mainPage = !pages.length ? this.add({ type: typeMain }, opt) : this.getMain();
this.select(mainPage, opt);
},
onLoad() {
const { pages } = this;
const opt = { silent: true };
pages.add(this.config.pages?.map(page => new Page(page, { em: this.em, config: this.config })) || [], opt);
const mainPage = !pages.length ? this.add({ type: typeMain }, opt) : this.getMain();
this.select(mainPage, opt);
}
_onPageChange(m, page, opts) {
const { em } = this;
const lm = em.get('LayerManager');
const mainComp = page.getMainComponent();
lm && mainComp && lm.setRoot(mainComp);
em.trigger(evPageSelect, page, m.previous('selected'));
this.__onChange(chnSel, page, opts);
},
_onPageChange(m, page, opts) {
const { em } = this;
const lm = em.get('LayerManager');
const mainComp = page.getMainComponent();
lm && mainComp && lm.setRoot(mainComp);
em.trigger(evPageSelect, page, m.previous('selected'));
this.__onChange(chnSel, page, opts);
}
postLoad() {
const { em, model } = this;
const um = em.get('UndoManager');
um && um.add(model);
um && um.add(this.pages);
},
postLoad() {
const { em, model } = this;
const um = em.get('UndoManager');
um && um.add(model);
um && um.add(this.pages);
}
/**
* Add new page
* @param {Object} props Page properties
* @param {Object} [opts] Options
* @returns {[Page]}
* @example
* const newPage = pageManager.add({
* id: 'new-page-id', // without an explicit ID, a random one will be created
* styles: `.my-class { color: red }`, // or a JSON of styles
* component: '<div class="my-class">My element</div>', // or a JSON of components
* });
*/
add(props, opts = {}) {
const { em } = this;
props.id = props.id || this._createId();
const add = () => {
const page = this.pages.add(new Page(props, { em: this.em, config: this.config }), opts);
opts.select && this.select(page);
return page;
};
!opts.silent && em.trigger(evPageAddBefore, props, add, opts);
return !opts.abort && add();
},
/**
* Add new page
* @param {Object} props Page properties
* @param {Object} [opts] Options
* @returns {[Page]}
* @example
* const newPage = pageManager.add({
* id: 'new-page-id', // without an explicit ID, a random one will be created
* styles: `.my-class { color: red }`, // or a JSON of styles
* component: '<div class="my-class">My element</div>', // or a JSON of components
* });
*/
add(props, opts = {}) {
const { em } = this;
props.id = props.id || this._createId();
const add = () => {
const page = this.pages.add(new Page(props, { em: this.em, config: this.config }), opts);
opts.select && this.select(page);
return page;
};
!opts.silent && em.trigger(evPageAddBefore, props, add, opts);
return !opts.abort && add();
}
/**
* Remove page
* @param {String|[Page]} page Page or page id
* @returns {[Page]} Removed Page
* @example
* const removedPage = pageManager.remove('page-id');
* // or by passing the page
* const somePage = pageManager.get('page-id');
* pageManager.remove(somePage);
*/
remove(page, opts = {}) {
const { em } = this;
const pg = isString(page) ? this.get(page) : page;
const rm = () => {
pg && this.pages.remove(pg, opts);
return pg;
};
!opts.silent && em.trigger(evPageRemoveBefore, pg, rm, opts);
return !opts.abort && rm();
},
/**
* Remove page
* @param {String|[Page]} page Page or page id
* @returns {[Page]} Removed Page
* @example
* const removedPage = pageManager.remove('page-id');
* // or by passing the page
* const somePage = pageManager.get('page-id');
* pageManager.remove(somePage);
*/
remove(page, opts = {}) {
const { em } = this;
const pg = isString(page) ? this.get(page) : page;
const rm = () => {
pg && this.pages.remove(pg, opts);
return pg;
};
!opts.silent && em.trigger(evPageRemoveBefore, pg, rm, opts);
return !opts.abort && rm();
}
/**
* Get page by id
* @param {String} id Page id
* @returns {[Page]}
* @example
* const somePage = pageManager.get('page-id');
*/
get(id) {
return this.pages.filter(p => p.get('id') === id)[0];
},
/**
* Get page by id
* @param {String} id Page id
* @returns {[Page]}
* @example
* const somePage = pageManager.get('page-id');
*/
get(id) {
return this.pages.filter(p => p.get('id') === id)[0];
}
/**
* Get main page (the first one available)
* @returns {[Page]}
* @example
* const mainPage = pageManager.getMain();
*/
getMain() {
const { pages } = this;
return pages.filter(p => p.get('type') === typeMain)[0] || pages.at(0);
},
/**
* Get main page (the first one available)
* @returns {[Page]}
* @example
* const mainPage = pageManager.getMain();
*/
getMain() {
const { pages } = this;
return pages.filter(p => p.get('type') === typeMain)[0] || pages.at(0);
}
/**
* Get all pages
* @returns {Array<[Page]>}
* @example
* const arrayOfPages = pageManager.getAll();
*/
getAll() {
return [...this.pages.models];
},
/**
* Get all pages
* @returns {Array<[Page]>}
* @example
* const arrayOfPages = pageManager.getAll();
*/
getAll() {
return [...this.pages.models];
}
/**
* Get wrapper components (aka body) from all pages and frames.
* @returns {Array<[Component]>}
* @example
* const wrappers = pageManager.getAllWrappers();
* // Get all `image` components from the project
* const allImages = wrappers.map(wrp => wrp.findType('image')).flat();
*/
getAllWrappers() {
const pages = this.getAll();
return unique(flatten(pages.map(page => page.getAllFrames().map(frame => frame.getComponent()))));
},
/**
* Get wrapper components (aka body) from all pages and frames.
* @returns {Array<[Component]>}
* @example
* const wrappers = pageManager.getAllWrappers();
* // Get all `image` components from the project
* const allImages = wrappers.map(wrp => wrp.findType('image')).flat();
*/
getAllWrappers() {
const pages = this.getAll();
return unique(flatten(pages.map(page => page.getAllFrames().map(frame => frame.getComponent()))));
}
getAllMap() {
return this.getAll().reduce((acc, i) => {
acc[i.get('id')] = i;
return acc;
}, {});
},
getAllMap() {
return this.getAll().reduce((acc, i) => {
acc[i.get('id')] = i;
return acc;
}, {});
}
/**
* Change the selected page. This will switch the page rendered in canvas
* @param {String|[Page]} page Page or page id
* @returns {this}
* @example
* pageManager.select('page-id');
* // or by passing the page
* const somePage = pageManager.get('page-id');
* pageManager.select(somePage);
*/
select(page, opts = {}) {
const pg = isString(page) ? this.get(page) : page;
if (pg) {
this.em.trigger(evPageSelectBefore, pg, opts);
this.model.set('selected', pg, opts);
}
return this;
},
/**
* Change the selected page. This will switch the page rendered in canvas
* @param {String|[Page]} page Page or page id
* @returns {this}
* @example
* pageManager.select('page-id');
* // or by passing the page
* const somePage = pageManager.get('page-id');
* pageManager.select(somePage);
*/
select(page, opts = {}) {
const pg = isString(page) ? this.get(page) : page;
if (pg) {
this.em.trigger(evPageSelectBefore, pg, opts);
this.model.set('selected', pg, opts);
}
return this;
}
/**
* Get the selected page
* @returns {[Page]}
* @example
* const selectedPage = pageManager.getSelected();
*/
getSelected() {
return this.model.get('selected');
},
/**
* Get the selected page
* @returns {[Page]}
* @example
* const selectedPage = pageManager.getSelected();
*/
getSelected() {
return this.model.get('selected');
}
destroy() {
this.pages.off().reset();
this.model.stopListening();
this.model.clear({ silent: true });
['selected', 'config', 'em', 'pages', 'model'].map(i => (this[i] = 0));
},
destroy() {
this.pages.off().reset();
this.model.stopListening();
this.model.clear({ silent: true });
['selected', 'model'].map(i => (this[i] = 0));
}
store() {
return this.getProjectData();
},
store() {
return this.getProjectData();
}
load(data) {
return this.loadProjectData(data, { all: this.pages, reset: true });
},
load(data) {
return this.loadProjectData(data, { all: this.pages, reset: true });
}
_createId() {
const pages = this.getAll();
const len = pages.length + 16;
const pagesMap = this.getAllMap();
let id;
_createId() {
const pages = this.getAll();
const len = pages.length + 16;
const pagesMap = this.getAllMap();
let id;
do {
id = createId(len);
} while (pagesMap[id]);
do {
id = createId(len);
} while (pagesMap[id]);
return id;
},
};
};
return id;
}
}

7
src/pages/model/Page.ts

@ -24,8 +24,11 @@ export default class Page extends Model {
defFrame.styles = props.styles;
["component", "styles"].map((i) => this.unset(i));
}
const frms = props.frames || [defFrame];
const frames = new Frames(frms, config);
const frms: any[] = props.frames || [defFrame];
const frames = new Frames(
frms?.map((model) => new Frame(model, opts)),
opts
);
frames.page = this;
this.set("frames", frames);
const um = em && em.get("UndoManager");

Loading…
Cancel
Save