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);
},
/**
* Store assets data to the selected storage
* @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;
store() {
return this.getProjectData();
},
/**
* Load data from the passed object.
* 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;
load(data) {
return this.loadProjectData(data);
},
/**

34
src/common/module.js

@ -6,6 +6,36 @@ export default {
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) {
const res = this.config || {};
return name ? res[name] : res;
@ -82,8 +112,8 @@ export default {
__onAllEvent() {},
__logWarn(str) {
this.em.logWarning(`[${this.name}]: ${str}`);
__logWarn(str, opts) {
this.em.logWarning(`[${this.name}]: ${str}`, opts);
},
_createId(len = 16) {

69
src/css_composer/index.js

@ -35,6 +35,7 @@ import CssRules from './model/CssRules';
import CssRulesView from './view/CssRulesView';
import Selectors from 'selector_manager/model/Selectors';
import Selector from 'selector_manager/model/Selector';
import Module from 'common/module';
export default () => {
let em;
@ -42,6 +43,8 @@ export default () => {
var rules, rulesView;
return {
...Module,
Selectors,
/**
@ -51,23 +54,12 @@ export default () => {
*/
name: 'CssComposer',
storageKey: 'styles',
getConfig() {
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
* @param {Object} config Configurations
@ -108,55 +100,12 @@ export default () => {
um && um.add(this.getAll());
},
/**
* Load data from the passed object, if the object is empty will try to fetch them
* 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;
store() {
return this.getProjectData();
},
/**
* Store data to the selected storage
* @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;
load(data) {
return this.loadProjectData(data);
},
/**

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)
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.
* It might be useful to transform custom objects (es. some framework specific JSX) to GrapesJS component one.
@ -53,6 +46,6 @@ export default {
'param',
'source',
'track',
'wbr'
]
'wbr',
],
};

103
src/dom_components/index.js

@ -96,6 +96,7 @@ import ComponentTextView from './view/ComponentTextView';
import ComponentWrapper from './model/ComponentWrapper';
import ComponentFrame from './model/ComponentFrame';
import ComponentFrameView from './view/ComponentFrameView';
import Module from 'common/module';
export default () => {
var c = {};
@ -207,6 +208,8 @@ export default () => {
];
return {
...Module,
Component,
Components,
@ -224,6 +227,8 @@ export default () => {
*/
name: 'DomComponents',
storageKey: 'components',
/**
* Returns config
* @return {Object} Config object
@ -233,19 +238,6 @@ export default () => {
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
* inside 'domComponents' field
@ -295,82 +287,21 @@ export default () => {
c.components && this.setComponents(c.components, { silent: 1 });
},
/**
* Load components from the passed object, if the object is empty will try to fetch them
* autonomously from the selected storage
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded data
*/
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);
load(data) {
return this.loadProjectData(data, {
onResult: result => {
if (isArray(result)) {
result.length && this.getComponents().reset(result);
} else {
this.getWrapper().set(result);
}
}
} 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 components on the selected storage
* @param {Boolean} noStore If true, won't store
* @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;
store() {
if (this.em.get('hasPages')) return {};
return this.getProjectData(this.getWrapper());
},
/**

5
src/editor/model/Editor.js

@ -611,10 +611,11 @@ export default class EditorModel extends Model {
* @return {Object} Stored data
* @private
*/
store(clb) {
store(opts) {
const sm = this.get('StorageManager');
if (!sm) return;
const isCallback = isFunction(opts);
const store = this.storeData();
sm.store(store, res => {
clb && clb(res, store);
@ -634,7 +635,7 @@ export default class EditorModel extends Model {
this.get('storables').forEach(m => {
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 { createId } from 'utils/mixins';
import { Model } from 'backbone';
import { Model, Module } from 'common';
import Pages from './model/Pages';
import Page from './model/Page';
@ -64,6 +64,8 @@ const typeMain = 'main';
export default () => {
return {
...Module,
name: 'PageManager',
storageKey: 'pages',
@ -80,7 +82,7 @@ export default () => {
add: evPageAdd,
addBefore: evPageAddBefore,
remove: evPageRemove,
removeBefore: evPageRemoveBefore
removeBefore: evPageRemoveBefore,
},
/**
@ -119,9 +121,7 @@ export default () => {
const { pages } = this;
const opt = { silent: true };
pages.add(this.config.pages || [], opt);
const mainPage = !pages.length
? this.add({ type: typeMain }, opt)
: this.getMain();
const mainPage = !pages.length ? this.add({ type: typeMain }, opt) : this.getMain();
this.select(mainPage, opt);
},
@ -228,13 +228,7 @@ export default () => {
*/
getAllWrappers() {
const pages = this.getAll();
return unique(
flatten(
pages.map(page =>
page.getAllFrames().map(frame => frame.getComponent())
)
)
);
return unique(flatten(pages.map(page => page.getAllFrames().map(frame => frame.getComponent()))));
},
getAllMap() {
@ -280,28 +274,13 @@ export default () => {
['selected', 'config', 'em', 'pages', 'model'].map(i => (this[i] = 0));
},
store(noStore) {
store() {
if (!this.em.get('hasPages')) return {};
const obj = {};
const cnf = this.config;
obj[this.storageKey] = JSON.stringify(this.getAll());
if (!noStore && cnf.stm) cnf.stm.store(obj);
return obj;
return this.getProjectData();
},
load(data = {}) {
const key = this.storageKey;
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;
load(data) {
return this.loadProjectData(data, { all: this.pages });
},
_createId() {
@ -315,6 +294,6 @@ export default () => {
} while (pagesMap[id]);
return id;
}
},
};
};

41
src/storage_manager/config/config.js

@ -31,6 +31,47 @@ export default {
// If enabled, checks if browser supports Local Storage
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
// Custom parameters to pass with the remote storage request, eg. csrf token
params: {},

47
src/storage_manager/index.js

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

Loading…
Cancel
Save