Browse Source

Refactor storage in modules

pull/4223/head
Artur Arseniev 4 years ago
parent
commit
046e10c528
  1. 43
      src/asset_manager/index.js
  2. 34
      src/common/module.js
  3. 69
      src/css_composer/index.js
  4. 11
      src/dom_components/config/config.js
  5. 103
      src/dom_components/index.js
  6. 5
      src/editor/model/Editor.js
  7. 43
      src/pages/index.js
  8. 41
      src/storage_manager/config/config.js
  9. 47
      src/storage_manager/index.js

43
src/asset_manager/index.js

@ -280,47 +280,12 @@ export default () => {
return this.__remove(asset, opts); return this.__remove(asset, opts);
}, },
/** store() {
* Store assets data to the selected storage return this.getProjectData();
* @param {Boolean} noStore If true, won't store
* @returns {Object} Data to store
* @example
* var assets = assetManager.store();
*/
store(noStore) {
const obj = {};
const assets = JSON.stringify(this.getAll().toJSON());
obj[this.storageKey] = assets;
if (!noStore && c.stm) c.stm.store(obj);
return obj;
}, },
/** load(data) {
* Load data from the passed object. return this.loadProjectData(data);
* The fetched data will be added to the collection.
* @param {Object} data Object of data to load
* @returns {Object} Loaded assets
* @example
* var assets = assetManager.load({
* assets: [...]
* })
*
*/
load(data = {}) {
const name = this.storageKey;
let assets = data[name] || [];
if (typeof assets == 'string') {
try {
assets = JSON.parse(data[name]);
} catch (err) {}
}
if (assets && assets.length) {
this.getAll().reset(assets);
}
return assets;
}, },
/** /**

34
src/common/module.js

@ -6,6 +6,36 @@ export default {
return this.__getConfig(name); return this.__getConfig(name);
}, },
getProjectData(data) {
const obj = {};
const key = this.storageKey;
if (key) {
obj[key] = data || this.getAll();
}
return obj;
},
loadProjectData(data = {}, { all, def = [], onResult } = {}) {
const key = this.storageKey;
let result = data[key] || def;
if (typeof result == 'string') {
try {
result = JSON.parse(result);
} catch (err) {
this.__logWarn('Data parsing failed', { input: result });
}
}
if (onResult) {
onResult(result);
} else if (result && result.length) {
(all || this.getAll()).reset(result);
}
return result;
},
__getConfig(name) { __getConfig(name) {
const res = this.config || {}; const res = this.config || {};
return name ? res[name] : res; return name ? res[name] : res;
@ -82,8 +112,8 @@ export default {
__onAllEvent() {}, __onAllEvent() {},
__logWarn(str) { __logWarn(str, opts) {
this.em.logWarning(`[${this.name}]: ${str}`); this.em.logWarning(`[${this.name}]: ${str}`, opts);
}, },
_createId(len = 16) { _createId(len = 16) {

69
src/css_composer/index.js

@ -35,6 +35,7 @@ import CssRules from './model/CssRules';
import CssRulesView from './view/CssRulesView'; import CssRulesView from './view/CssRulesView';
import Selectors from 'selector_manager/model/Selectors'; import Selectors from 'selector_manager/model/Selectors';
import Selector from 'selector_manager/model/Selector'; import Selector from 'selector_manager/model/Selector';
import Module from 'common/module';
export default () => { export default () => {
let em; let em;
@ -42,6 +43,8 @@ export default () => {
var rules, rulesView; var rules, rulesView;
return { return {
...Module,
Selectors, Selectors,
/** /**
@ -51,23 +54,12 @@ export default () => {
*/ */
name: 'CssComposer', name: 'CssComposer',
storageKey: 'styles',
getConfig() { getConfig() {
return c; return c;
}, },
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey() {
var keys = [];
var smc = (c.stm && c.stm.getConfig()) || {};
if (smc.storeCss) keys.push('css');
if (smc.storeStyles) keys.push('styles');
return keys;
},
/** /**
* Initializes module. Automatically called with a new instance of the editor * Initializes module. Automatically called with a new instance of the editor
* @param {Object} config Configurations * @param {Object} config Configurations
@ -108,55 +100,12 @@ export default () => {
um && um.add(this.getAll()); um && um.add(this.getAll());
}, },
/** store() {
* Load data from the passed object, if the object is empty will try to fetch them return this.getProjectData();
* autonomously from the storage manager.
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded rules
* @private
*/
load(data) {
var d = data || '';
if (!d && c.stm) {
d = c.em.getCacheLoad();
}
var obj = d.styles || '';
if (d.styles) {
try {
obj = JSON.parse(d.styles);
} catch (err) {}
} else if (d.css) {
obj = c.em.get('Parser').parseCss(d.css);
}
if (isArray(obj)) {
obj.length && rules.reset(obj);
} else if (obj) {
rules.reset(obj);
}
return obj;
}, },
/** load(data) {
* Store data to the selected storage return this.loadProjectData(data);
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
* @private
*/
store(noStore) {
if (!c.stm) return;
const obj = {};
const keys = this.storageKey();
const hasPages = em && em.get('hasPages');
if (keys.indexOf('css') >= 0 && !hasPages) obj.css = c.em.getCss();
if (keys.indexOf('styles') >= 0) obj.styles = JSON.stringify(rules);
if (!noStore) c.stm.store(obj);
return obj;
}, },
/** /**

11
src/dom_components/config/config.js

@ -7,13 +7,6 @@ export default {
// If the component is draggable you can drag the component itself (not only from the toolbar) // If the component is draggable you can drag the component itself (not only from the toolbar)
draggableComponents: 1, draggableComponents: 1,
// Generally, if you don't edit the wrapper in the editor, like
// custom attributes, you don't need the wrapper stored in your JSON
// structure, but in case you need it you can use this option.
// If you have `config.avoidInlineStyle` disabled the wrapper will be stored
// as we need to store inlined style.
storeWrapper: 0,
/** /**
* You can setup a custom component definition processor before adding it into the editor. * You can setup a custom component definition processor before adding it into the editor.
* It might be useful to transform custom objects (es. some framework specific JSX) to GrapesJS component one. * It might be useful to transform custom objects (es. some framework specific JSX) to GrapesJS component one.
@ -53,6 +46,6 @@ export default {
'param', 'param',
'source', 'source',
'track', 'track',
'wbr' 'wbr',
] ],
}; };

103
src/dom_components/index.js

@ -96,6 +96,7 @@ import ComponentTextView from './view/ComponentTextView';
import ComponentWrapper from './model/ComponentWrapper'; import ComponentWrapper from './model/ComponentWrapper';
import ComponentFrame from './model/ComponentFrame'; import ComponentFrame from './model/ComponentFrame';
import ComponentFrameView from './view/ComponentFrameView'; import ComponentFrameView from './view/ComponentFrameView';
import Module from 'common/module';
export default () => { export default () => {
var c = {}; var c = {};
@ -207,6 +208,8 @@ export default () => {
]; ];
return { return {
...Module,
Component, Component,
Components, Components,
@ -224,6 +227,8 @@ export default () => {
*/ */
name: 'DomComponents', name: 'DomComponents',
storageKey: 'components',
/** /**
* Returns config * Returns config
* @return {Object} Config object * @return {Object} Config object
@ -233,19 +238,6 @@ export default () => {
return c; return c;
}, },
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey() {
var keys = [];
var smc = (c.stm && c.stm.getConfig()) || {};
if (smc.storeHtml) keys.push('html');
if (smc.storeComponents) keys.push('components');
return keys;
},
/** /**
* Initialize module. Called on a new instance of the editor with configurations passed * Initialize module. Called on a new instance of the editor with configurations passed
* inside 'domComponents' field * inside 'domComponents' field
@ -295,82 +287,21 @@ export default () => {
c.components && this.setComponents(c.components, { silent: 1 }); c.components && this.setComponents(c.components, { silent: 1 });
}, },
/** load(data) {
* Load components from the passed object, if the object is empty will try to fetch them return this.loadProjectData(data, {
* autonomously from the selected storage onResult: result => {
* The fetched data will be added to the collection if (isArray(result)) {
* @param {Object} data Object of data to load result.length && this.getComponents().reset(result);
* @return {Object} Loaded data } else {
*/ this.getWrapper().set(result);
load(data = '') {
const { em } = this;
let result = '';
if (!data && c.stm) {
data = c.em.getCacheLoad();
}
const { components, html } = data;
if (components) {
if (isObject(components) || isArray(components)) {
result = components;
} else {
try {
result = JSON.parse(components);
} catch (err) {
em && em.logError(err);
} }
} },
} else if (html) { });
result = html;
}
const isObj = result && result.constructor === Object;
if ((result && result.length) || isObj) {
this.clear();
// If the result is an object I consider it the wrapper
if (isObj) {
this.getWrapper().set(result);
} else {
this.getComponents().add(result);
}
}
return result;
}, },
/** store() {
* Store components on the selected storage if (this.em.get('hasPages')) return {};
* @param {Boolean} noStore If true, won't store return this.getProjectData(this.getWrapper());
* @return {Object} Data to store
*/
store(noStore) {
if (!c.stm || this.em.get('hasPages')) {
return {};
}
var obj = {};
var keys = this.storageKey();
if (keys.indexOf('html') >= 0) {
obj.html = c.em.getHtml();
}
if (keys.indexOf('components') >= 0) {
// const storeWrap = (em && !em.getConfig('avoidInlineStyle')) || c.storeWrapper;
const storeWrap = c.storeWrapper;
const toStore = storeWrap ? this.getWrapper() : this.getComponents();
obj.components = JSON.stringify(toStore);
}
if (!noStore) {
c.stm.store(obj);
}
return obj;
}, },
/** /**

5
src/editor/model/Editor.js

@ -611,10 +611,11 @@ export default class EditorModel extends Model {
* @return {Object} Stored data * @return {Object} Stored data
* @private * @private
*/ */
store(clb) { store(opts) {
const sm = this.get('StorageManager'); const sm = this.get('StorageManager');
if (!sm) return; if (!sm) return;
const isCallback = isFunction(opts);
const store = this.storeData(); const store = this.storeData();
sm.store(store, res => { sm.store(store, res => {
clb && clb(res, store); clb && clb(res, store);
@ -634,7 +635,7 @@ export default class EditorModel extends Model {
this.get('storables').forEach(m => { this.get('storables').forEach(m => {
result = { ...result, ...m.store(1) }; result = { ...result, ...m.store(1) };
}); });
return result; return JSON.parse(JSON.stringify(result));
} }
/** /**

43
src/pages/index.js

@ -46,7 +46,7 @@
import { isString, bindAll, unique, flatten } from 'underscore'; import { isString, bindAll, unique, flatten } from 'underscore';
import { createId } from 'utils/mixins'; import { createId } from 'utils/mixins';
import { Model } from 'backbone'; import { Model, Module } from 'common';
import Pages from './model/Pages'; import Pages from './model/Pages';
import Page from './model/Page'; import Page from './model/Page';
@ -64,6 +64,8 @@ const typeMain = 'main';
export default () => { export default () => {
return { return {
...Module,
name: 'PageManager', name: 'PageManager',
storageKey: 'pages', storageKey: 'pages',
@ -80,7 +82,7 @@ export default () => {
add: evPageAdd, add: evPageAdd,
addBefore: evPageAddBefore, addBefore: evPageAddBefore,
remove: evPageRemove, remove: evPageRemove,
removeBefore: evPageRemoveBefore removeBefore: evPageRemoveBefore,
}, },
/** /**
@ -119,9 +121,7 @@ export default () => {
const { pages } = this; const { pages } = this;
const opt = { silent: true }; const opt = { silent: true };
pages.add(this.config.pages || [], opt); pages.add(this.config.pages || [], opt);
const mainPage = !pages.length const mainPage = !pages.length ? this.add({ type: typeMain }, opt) : this.getMain();
? this.add({ type: typeMain }, opt)
: this.getMain();
this.select(mainPage, opt); this.select(mainPage, opt);
}, },
@ -228,13 +228,7 @@ export default () => {
*/ */
getAllWrappers() { getAllWrappers() {
const pages = this.getAll(); const pages = this.getAll();
return unique( return unique(flatten(pages.map(page => page.getAllFrames().map(frame => frame.getComponent()))));
flatten(
pages.map(page =>
page.getAllFrames().map(frame => frame.getComponent())
)
)
);
}, },
getAllMap() { getAllMap() {
@ -280,28 +274,13 @@ export default () => {
['selected', 'config', 'em', 'pages', 'model'].map(i => (this[i] = 0)); ['selected', 'config', 'em', 'pages', 'model'].map(i => (this[i] = 0));
}, },
store(noStore) { store() {
if (!this.em.get('hasPages')) return {}; if (!this.em.get('hasPages')) return {};
const obj = {}; return this.getProjectData();
const cnf = this.config;
obj[this.storageKey] = JSON.stringify(this.getAll());
if (!noStore && cnf.stm) cnf.stm.store(obj);
return obj;
}, },
load(data = {}) { load(data) {
const key = this.storageKey; return this.loadProjectData(data, { all: this.pages });
let res = data[key] || [];
if (typeof res == 'string') {
try {
res = JSON.parse(data[key]);
} catch (err) {}
}
res && res.length && this.pages.reset(res);
return res;
}, },
_createId() { _createId() {
@ -315,6 +294,6 @@ export default () => {
} while (pagesMap[id]); } while (pagesMap[id]);
return id; return id;
} },
}; };
}; };

41
src/storage_manager/config/config.js

@ -31,6 +31,47 @@ export default {
// If enabled, checks if browser supports Local Storage // If enabled, checks if browser supports Local Storage
checkLocal: true, checkLocal: true,
// Default storage options
options: {
local: {
key: 'gjs-project',
},
remote: {
// Custom parameters to pass with the remote request, eg. csrf token
params: {},
// Custom headers
headers: {},
// Endpoint where to save all stuff
urlStore: '',
// Endpoint where to fetch data
urlLoad: '',
//Callback before request
beforeSend(jqXHR, settings) {},
//Callback after request
onComplete(jqXHR, status) {},
// set contentType paramater of $.ajax
// true: application/json; charset=utf-8'
// false: 'x-www-form-urlencoded'
contentTypeJson: true,
// Pass custom options to fetch API (remote storage)
// You can pass a simple object: { someOption: 'someValue' }
// or a function which returns and object to add:
// currentOpts => {
// return currentOpts.method === 'post' ? { method: 'patch' } : {};
// }
fetchOptions: '',
credentials: 'include',
/**
* (TODO) This will enable the store of the project also on the local storage.
* The local data are cleared on every sucessful remote save. In case the remote storage
* fails (eg. network issue), on project reload, a dialog with the possibility to recovery
* previous data will be shown.
*/
recovery: true,
},
},
// ONLY FOR REMOTE STORAGE // ONLY FOR REMOTE STORAGE
// Custom parameters to pass with the remote storage request, eg. csrf token // Custom parameters to pass with the remote storage request, eg. csrf token
params: {}, params: {},

47
src/storage_manager/index.js

@ -213,35 +213,32 @@ export default () => {
}, },
/** /**
* Store key-value resources in the current storage * Store data in the current storage.
* @param {Object} data Data in key-value format, eg. {item1: value1, item2: value2} * @param {Object} data Data in key-value format, eg. `{ item1: value1, item2: value2 }`
* @param {Function} clb Callback function * @param {Function} resolve Resolve callback function. The result is passed as an argument.
* @param {Function} reject Reject callback function. The error is passed as an argument.
* @return {Object|null} * @return {Object|null}
* @example * @example
* storageManager.store({item1: value1, item2: value2}); * storageManager.store({item1: value1, item2: value2});
* */ * */
store(data, clb) { store(data, resolve, reject, options = {}) {
const st = this.get(this.getCurrent()); const st = this.getCurrentStorage();
const toStore = {}; const toStore = {};
const opts = { ...this.getCurrentOptons(), ...options };
this.onStart('store', data); this.onStart('store', data);
for (let key in data) { const onResult = res => {
toStore[c.id + key] = data[key]; this.onAfter('store', res);
} resolve?.(res);
this.onEnd('store', res);
};
const onError = err => {
reject?.(err);
this.onError('store', err);
};
return st return st ? st.store(toStore, onResult, onError, opts) : null;
? st.store(
toStore,
res => {
this.onAfter('store', res);
clb && clb(res);
this.onEnd('store', res);
},
err => {
this.onError('store', err);
}
)
: null;
}, },
/** /**
@ -257,7 +254,7 @@ export default () => {
* }); * });
* */ * */
load(keys, clb) { load(keys, clb) {
const st = this.get(this.getCurrent()); const st = this.getCurrentStorage();
const keysF = []; const keysF = [];
let result = {}; let result = {};
@ -323,6 +320,12 @@ export default () => {
return this.get(this.getCurrent()); return this.get(this.getCurrent());
}, },
getCurrentOptons() {
const config = this.getConfig();
const current = this.getCurrent();
return config.options[current] || {};
},
/** /**
* On start callback * On start callback
* @private * @private

Loading…
Cancel
Save