From d419bd11accfaeb62a1711c3d12c1169a632333a Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 29 Aug 2017 01:20:56 +0200 Subject: [PATCH] Refactor Asset Manager and add the possibility to insert custom types --- src/asset_manager/index.js | 118 +++++++++++++++--- src/asset_manager/model/Assets.js | 86 +++---------- src/asset_manager/view/AssetImageView.js | 87 ++++++------- src/asset_manager/view/AssetView.js | 15 ++- src/asset_manager/view/AssetsView.js | 112 ++++++++--------- src/commands/view/OpenAssets.js | 3 +- .../model/TypeableCollection.js | 36 ++++-- src/editor/index.js | 2 + 8 files changed, 256 insertions(+), 203 deletions(-) diff --git a/src/asset_manager/index.js b/src/asset_manager/index.js index 8063018bf..68ade299f 100644 --- a/src/asset_manager/index.js +++ b/src/asset_manager/index.js @@ -5,8 +5,13 @@ * * [remove](#remove) * * [store](#store) * * [load](#load) + * * [getContainer](#getcontainer) + * * [getAssetsEl](#getassetsel) * * [onClick](#onClick) * * [onDblClick](#onDblClick) + * * [addType](#addtype) + * * [getType](#gettype) + * * [getTypes](#gettypes) * * Before using this methods you should get first the module from the editor instance, in this way: * @@ -35,11 +40,12 @@ */ module.exports = () => { - var c = {}, - Assets = require('./model/Assets'), - AssetsView = require('./view/AssetsView'), - FileUpload = require('./view/FileUploader'), - assets, am, fu; + let c = {}; + const defaults = require('./config/config'); + const Assets = require('./model/Assets'); + const AssetsView = require('./view/AssetsView'); + const FileUpload = require('./view/FileUploader'); + let assets, am, fu; return { @@ -64,24 +70,42 @@ module.exports = () => { */ init(config) { c = config || {}; - var defaults = require('./config/config'); - for (var name in defaults) { + for (let name in defaults) { if (!(name in c)) c[name] = defaults[name]; } - var ppfx = c.pStylePrefix; - if(ppfx) + const ppfx = c.pStylePrefix; + const em = c.em; + + if (ppfx) { c.stylePrefix = ppfx + c.stylePrefix; + } + // Global assets collection assets = new Assets(c.assets); - var obj = { - collection: assets, + const obj = { + // Collection visible in asset manager + collection: new Assets([]), + globalCollection: assets, config: c, }; - am = new AssetsView(obj); fu = new FileUpload(obj); + obj.fu = fu; + am = new AssetsView(obj); + + // Setup the sync between the global and public collections + assets.listenTo(assets, 'add', (model) => { + this.getAllVisible().add(model); + em && em.trigger('asset:add', model); + }); + + assets.listenTo(assets, 'remove', (model) => { + this.getAllVisible().remove(model); + em && em.trigger('asset:remove', model); + }); + return this; }, @@ -123,13 +147,21 @@ module.exports = () => { }, /** - * Return all assets + * Return global collection * @return {Collection} */ getAll() { return assets; }, + /** + * Return visible collection + * @return {Collection} + */ + getAllVisible() { + return am.collection; + }, + /** * Remove the asset by its URL * @param {string} src URL of the asset @@ -194,22 +226,72 @@ module.exports = () => { return assets; }, + /** + * Return the Asset Manager Container + * @return {HTMLElement} + */ + getContainer() { + return am.el; + }, + + /** + * Get assets element container + * @return {HTMLElement} + */ + getAssetsEl() { + return am.el.querySelector('[data-el=assets]'); + }, + /** * Render assets - * @param {Boolean} f Force to render, otherwise cached version will be returned + * @param {Boolean} f Force to render, otherwise cached version will be returned * @return {HTMLElement} * @private */ - render(f) { - if(!this.rendered || f) - this.rendered = am.render().$el.add(fu.render().$el); - return this.rendered; + render(assets = []) { + const toRender = assets.length ? assets : this.getAll().models; + am.collection.reset(toRender); + return this.getContainer(); }, postRender(editorView) { c.dropzone && fu.initDropzone(editorView); }, + /** + * Add new type + * @param {string} id Type ID + * @param {Object} definition Definition of the type. Each definition contains + * `model` (business logic), `view` (presentation logic) + * and `isType` function which recognize the type of the + * passed entity + * addType('my-type', { + * model: {}, + * view: {}, + * isType: (value) => {}, + * }) + */ + addType(id, definition) { + this.getAll().addType(id, definition); + }, + + /** + * Get type + * @param {string} id Type ID + * @return {Object} Type definition + */ + getType(id) { + return this.getAll().getType(id); + }, + + /** + * Get types + * @return {Array} + */ + getTypes() { + return this.getAll().getTypes(); + }, + //------- /** diff --git a/src/asset_manager/model/Assets.js b/src/asset_manager/model/Assets.js index 7c6395db5..191b48bc7 100644 --- a/src/asset_manager/model/Assets.js +++ b/src/asset_manager/model/Assets.js @@ -1,68 +1,20 @@ -var Backbone = require('backbone'); -var Asset = require('./Asset'); -var AssetImage = require('./AssetImage'); - -module.exports = Backbone.Collection.extend({ - - model: AssetImage, - - initialize(models, opt) { - - this.model = (attrs, options) => { - var model; - switch(attrs.type){ - default: - model = new AssetImage(attrs, options); - } - return model; - }; - - }, - - /** - * Add new image asset to the collection - * @param {string} url URL of the image - * @param {Object} opts Options - * @return {this} - * @private - */ - addImg(url, opts) { - this.add({ - type: 'image', - src: url, - }, opts); - return this; - }, - - /** - * Prevent inserting assets with the same 'src' - * Seems like idAttribute is not working with dynamic model assignament - * @private - */ - add(models, opt) { - var mods = []; - models = models instanceof Array ? models : [models]; - - for (var i = 0, len = models.length; i < len; i++) { - var model = models[i]; - - if(typeof model === 'string') - model = {src: model, type: 'image'}; - - if(!model || !model.src) - continue; - - var found = this.where({src: model.src}); - - if(!found.length) - mods.push(model); - } - - if(mods.length == 1) - mods = mods[0]; - - return Backbone.Collection.prototype.add.apply(this, [mods, opt]); - }, - - +import TypeableCollection from 'domain_abstract/model/TypeableCollection'; + +module.exports = require('backbone').Collection.extend(TypeableCollection).extend({ + getTypes() { + return [{ + id: 'image', + model: require('./AssetImage'), + view: require('./../view/AssetImageView'), + isType(value) { + if (typeof value == 'string') { + return { + type: 'image', + src: value, + } + } + return value; + } + }]; + } }); diff --git a/src/asset_manager/view/AssetImageView.js b/src/asset_manager/view/AssetImageView.js index fd369e525..8a1d35305 100644 --- a/src/asset_manager/view/AssetImageView.js +++ b/src/asset_manager/view/AssetImageView.js @@ -1,31 +1,37 @@ -var AssetView = require('./AssetView'); -var assetTemplate = ` -
-
-
-
-
-
<%= name %>
-
<%= dim %>
-
-
-
-`; +module.exports = require('./AssetView').extend({ -module.exports = AssetView.extend({ - - events:{ - 'click': 'handleClick', - 'dblclick': 'handleDblClick', + events: { + click: 'handleClick', + dblclick: 'handleDblClick', + 'click [data-toggle=asset-remove]': 'removeItem', }, - template: _.template(assetTemplate), + template(view, model) { + const pfx = view.pfx; + const ppfx = view.ppfx; + let name = model.get('name'); + let width = model.get('width'); + let height = model.get('height'); + let unit = model.get('unitDim'); + let dim = width && height ? `${width}x${height}${unit}` : ''; + name = name || model.getFilename(); + return ` +
+
+
+
+
+
${name}
+
${dim}
+
+
+
+ `; + }, - initialize(o) { - AssetView.prototype.initialize.apply(this, arguments); - this.className += ' ' + this.pfx + 'asset-image'; - this.events['click #' + this.pfx + 'close'] = 'removeItem'; - this.delegateEvents(); + init(o) { + const pfx = this.pfx; + this.className += ` ${pfx}asset-image`; }, /** @@ -35,7 +41,7 @@ module.exports = AssetView.extend({ handleClick() { var onClick = this.config.onClick; var model = this.model; - model.collection.trigger('deselectAll'); + this.collection.trigger('deselectAll'); this.$el.addClass(this.pfx + 'highlight'); if (typeof onClick === 'function') { @@ -59,8 +65,8 @@ module.exports = AssetView.extend({ this.updateTarget(model.get('src')); } - var onSelect = model.collection.onSelect; - if(typeof onSelect == 'function'){ + var onSelect = this.collection.onSelect; + if (typeof onSelect == 'function') { onSelect(this.model); } }, @@ -71,9 +77,10 @@ module.exports = AssetView.extend({ * @private * */ updateTarget(v) { - var target = this.model.collection.target; - if(target && target.set) { - var attr = _.clone( target.get('attributes') ); + const target = this.collection.target; + + if (target && target.set) { + var attr = _.clone(target.get('attributes')); target.set('attributes', attr ); target.set('src', v ); } @@ -86,23 +93,5 @@ module.exports = AssetView.extend({ removeItem(e) { e.stopPropagation(); this.model.collection.remove(this.model); - }, - - render() { - var name = this.model.get('name'), - dim = this.model.get('width') && this.model.get('height') ? - this.model.get('width')+' x '+this.model.get('height') : ''; - name = name ? name : this.model.get('src').split("/").pop(); - name = name && name.length > 30 ? name.substring(0, 30)+'...' : name; - dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : ''; - this.$el.html( this.template({ - name, - src: this.model.get('src'), - dim, - pfx: this.pfx, - ppfx: this.ppfx - })); - this.$el.attr('class', this.className); - return this; - }, + } }); diff --git a/src/asset_manager/view/AssetView.js b/src/asset_manager/view/AssetView.js index 38ed331a1..43981747e 100644 --- a/src/asset_manager/view/AssetView.js +++ b/src/asset_manager/view/AssetView.js @@ -1,12 +1,23 @@ var Backbone = require('backbone'); module.exports = Backbone.View.extend({ - initialize(o) { + + initialize(o = {}) { this.options = o; + this.collection = o.collection; this.config = o.config || {}; this.pfx = this.config.stylePrefix || ''; this.ppfx = this.config.pStylePrefix || ''; this.className = this.pfx + 'asset'; - this.listenTo( this.model, 'destroy remove', this.remove); + this.listenTo(this.model, 'destroy remove', this.remove); + const init = this.init && this.init.bind(this); + init && init(o); + }, + + render() { + const el = this.el; + el.innerHTML = this.template(this, this.model); + el.className = this.className; + return this; }, }); diff --git a/src/asset_manager/view/AssetsView.js b/src/asset_manager/view/AssetsView.js index 94208064d..97a66d64e 100644 --- a/src/asset_manager/view/AssetsView.js +++ b/src/asset_manager/view/AssetsView.js @@ -2,30 +2,32 @@ var Backbone = require('backbone'); var AssetView = require('./AssetView'); var AssetImageView = require('./AssetImageView'); var FileUploader = require('./FileUploader'); -var assetsTemplate = ` -
-
-
-
- -
- -
-
- -
-
-
-
- -`; module.exports = Backbone.View.extend({ - template: _.template(assetsTemplate), + template(view) { + const pfx = view.pfx; + const ppfx = view.ppfx; + return ` +
+
+
+
+ +
+ +
+
+ +
+
+
+
+ `; + }, initialize(o) { this.options = o; @@ -35,7 +37,6 @@ module.exports = Backbone.View.extend({ this.listenTo(this.collection, 'add', this.addToAsset ); this.listenTo(this.collection, 'deselectAll', this.deselectAll); this.listenTo(this.collection, 'reset', this.render); - this.className = this.pfx + 'assets'; this.events = {}; this.events.submit = 'addFromStr'; @@ -50,16 +51,14 @@ module.exports = Backbone.View.extend({ */ addFromStr(e) { e.preventDefault(); + const input = this.getInputUrl(); + const url = input.value.trim(); - var input = this.getInputUrl(); - - var url = input.value.trim(); - - if(!url) + if (!url) { return; + } - this.collection.addImg(url, {at: 0}); - + this.options.globalCollection.add(url, {at: 0}); this.getAssetsEl().scrollTop = 0; input.value = ''; return this; @@ -72,8 +71,7 @@ module.exports = Backbone.View.extend({ */ getAssetsEl() { //if(!this.assets) // Not able to cache as after the rerender it losses the ref - this.assets = this.el.querySelector('.' + this.pfx + 'assets'); - return this.assets; + return this.el.querySelector(`.${this.pfx}assets`); }, /** @@ -83,7 +81,7 @@ module.exports = Backbone.View.extend({ */ getInputUrl() { if(!this.inputUrl || !this.inputUrl.value) - this.inputUrl = this.el.querySelector('.'+this.pfx+'add-asset input'); + this.inputUrl = this.el.querySelector(`.${this.pfx}add-asset input`); return this.inputUrl; }, @@ -102,25 +100,23 @@ module.exports = Backbone.View.extend({ * @return Object Object created * @private * */ - addAsset(model, fragmentEl) { - var fragment = fragmentEl || null; - var viewObject = AssetView; - - if(model.get('type').indexOf("image") > -1) - viewObject = AssetImageView; - - var view = new viewObject({ + addAsset(model, fragmentEl = null) { + const fragment = fragmentEl; + const collection = this.collection; + const config = this.config; + const rendered = new model.typeView({ model, - config : this.config, - }); - var rendered = view.render().el; + collection, + config, + }).render().el; - if(fragment){ + if (fragment) { fragment.appendChild( rendered ); - }else{ - var assetsEl = this.getAssetsEl(); - if(assetsEl) + } else { + const assetsEl = this.getAssetsEl(); + if (assetsEl) { assetsEl.insertBefore(rendered, assetsEl.firstChild); + } } return rendered; @@ -131,24 +127,24 @@ module.exports = Backbone.View.extend({ * @private * */ deselectAll() { - this.$el.find('.' + this.pfx + 'highlight').removeClass(this.pfx + 'highlight'); + const pfx = this.pfx; + this.$el.find(`.${pfx}highlight`).removeClass(`${pfx}highlight`); }, render() { - var fragment = document.createDocumentFragment(); + const pfx = this.pfx; + const ppfx = this.ppfx; + const fuRendered = this.options.fu.render().el; + const fragment = document.createDocumentFragment(); this.$el.empty(); - this.collection.each(function(model){ + this.collection.each((model) => { this.addAsset(model, fragment); - },this); - - this.$el.html(this.template({ - pfx: this.pfx, - ppfx: this.ppfx, - btnText: this.config.addBtnText, - })); + }); - this.$el.find('.'+this.pfx + 'assets').append(fragment); + this.$el.append(fuRendered).append(this.template(this)); + this.el.className = `${ppfx}asset-manager`; + this.$el.find(`.${pfx}assets`).append(fragment); return this; } }); diff --git a/src/commands/view/OpenAssets.js b/src/commands/view/OpenAssets.js index 17714a317..8cbb20e3e 100644 --- a/src/commands/view/OpenAssets.js +++ b/src/commands/view/OpenAssets.js @@ -12,9 +12,10 @@ module.exports = { // old API assetManager.setTarget(opt.target); assetManager.onSelect(opt.onSelect); + assetManager.render(); modal.setTitle(opt.modalTitle || 'Select image'); - modal.setContent(assetManager.render()); + modal.setContent(assetManager.getContainer()); modal.open(); }, diff --git a/src/domain_abstract/model/TypeableCollection.js b/src/domain_abstract/model/TypeableCollection.js index a5278480f..4260b37e5 100644 --- a/src/domain_abstract/model/TypeableCollection.js +++ b/src/domain_abstract/model/TypeableCollection.js @@ -4,13 +4,24 @@ const View = Backbone.View; export default { initialize(models, opts) { - this.model = (attrs, options) => { - let modelType = this.getType(attrs.type); - const baseType = this.getBaseType(); - const Model = modelType ? modelType.model : baseType.model; - return new Model(attrs, options); + this.model = (attrs = {}, options = {}) => { + let Model, type; + + if (attrs && attrs.type) { + type = this.getType(attrs.type); + Model = type ? type.model : this.getBaseType().model; + } else { + const typeFound = this.recognizeType(attrs); + type = typeFound.type; + Model = type.model; + attrs = typeFound.attributes; + } + + const model = new Model(attrs, options); + model.typeView = type.view; + return model; }; - const init = this.init; + const init = this.init && this.init.bind(this); init && init(); }, @@ -29,9 +40,18 @@ export default { {type: type.id} : typeFound; if (typeFound) { - return typeFound; + return { + type, + attributes: typeFound, + }; } } + + // If, for any reason, the type is not found it'll return the base one + return { + type: this.getBaseType(), + attributes: value, + } }, /** @@ -54,7 +74,7 @@ export default { /** * Get type * @param {string} id Type ID - * + * @return {Object} Type definition */ getType(id) { const types = this.getTypes(); diff --git a/src/editor/index.js b/src/editor/index.js index dc8ec0739..631272664 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -37,6 +37,8 @@ * * `component:update:{propertyName}` - Listen any property change * * `component:styleUpdate` - Triggered when the style of the component is updated * * `component:styleUpdate:{propertyName}` - Listen for a specific style property change + * * `asset:add` - New asset added + * * `asset:remove` - Asset removed * * `asset:upload:start` - Before the upload is started * * `asset:upload:end` - After the upload is ended * * `asset:upload:error` - On any error in upload, passes the error as an argument