From 046e10c5286923eb36776ccf76f960fad39f9180 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 25 Feb 2022 16:32:38 +0100 Subject: [PATCH] Refactor storage in modules --- src/asset_manager/index.js | 43 ++--------- src/common/module.js | 34 ++++++++- src/css_composer/index.js | 69 +++--------------- src/dom_components/config/config.js | 11 +-- src/dom_components/index.js | 103 +++++---------------------- src/editor/model/Editor.js | 5 +- src/pages/index.js | 43 +++-------- src/storage_manager/config/config.js | 41 +++++++++++ src/storage_manager/index.js | 47 ++++++------ 9 files changed, 144 insertions(+), 252 deletions(-) diff --git a/src/asset_manager/index.js b/src/asset_manager/index.js index bd0c939ef..756402ffe 100644 --- a/src/asset_manager/index.js +++ b/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); }, /** diff --git a/src/common/module.js b/src/common/module.js index 8794a0542..8d1b1851b 100644 --- a/src/common/module.js +++ b/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) { diff --git a/src/css_composer/index.js b/src/css_composer/index.js index 939c7ae2e..eb375c12e 100644 --- a/src/css_composer/index.js +++ b/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); }, /** diff --git a/src/dom_components/config/config.js b/src/dom_components/config/config.js index cc895ee7c..b882cb42b 100644 --- a/src/dom_components/config/config.js +++ b/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', + ], }; diff --git a/src/dom_components/index.js b/src/dom_components/index.js index d508b20dc..c4a66558a 100644 --- a/src/dom_components/index.js +++ b/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()); }, /** diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js index ca3029d09..4f74307cb 100644 --- a/src/editor/model/Editor.js +++ b/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)); } /** diff --git a/src/pages/index.js b/src/pages/index.js index 65fb5e0d2..28489348b 100644 --- a/src/pages/index.js +++ b/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; - } + }, }; }; diff --git a/src/storage_manager/config/config.js b/src/storage_manager/config/config.js index 8d2c43bba..59027500f 100644 --- a/src/storage_manager/config/config.js +++ b/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: {}, diff --git a/src/storage_manager/index.js b/src/storage_manager/index.js index fd39e8d7a..8853daf4c 100644 --- a/src/storage_manager/index.js +++ b/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