Browse Source

Refactor Asset Manager and add the possibility to insert custom types

pull/261/head
Artur Arseniev 9 years ago
parent
commit
d419bd11ac
  1. 118
      src/asset_manager/index.js
  2. 86
      src/asset_manager/model/Assets.js
  3. 87
      src/asset_manager/view/AssetImageView.js
  4. 15
      src/asset_manager/view/AssetView.js
  5. 112
      src/asset_manager/view/AssetsView.js
  6. 3
      src/commands/view/OpenAssets.js
  7. 36
      src/domain_abstract/model/TypeableCollection.js
  8. 2
      src/editor/index.js

118
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();
},
//-------
/**

86
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;
}
}];
}
});

87
src/asset_manager/view/AssetImageView.js

@ -1,31 +1,37 @@
var AssetView = require('./AssetView');
var assetTemplate = `
<div id="<%= pfx %>preview-cont">
<div id="<%= pfx %>preview" style="background-image: url(<%= src %>);"></div>
<div id="<%= pfx %>preview-bg" class="<%= ppfx %>checker-bg"></div>
</div>
<div id="<%= pfx %>meta">
<div id="<%= pfx %>name"><%= name %></div>
<div id="<%= pfx %>dimensions"><%= dim %></div>
</div>
<div id="<%= pfx %>close">&Cross;</div>
<div style="clear:both"></div>
`;
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 `
<div id="${pfx}preview-cont">
<div id="${pfx}preview" style="background-image: url(${model.get('src')});"></div>
<div id="${pfx}preview-bg" class="${ppfx}checker-bg"></div>
</div>
<div id="${pfx}meta">
<div id="${pfx}name">${name}</div>
<div id="${pfx}dimensions">${dim}</div>
</div>
<div id="${pfx}close" data-toggle="asset-remove">&Cross;</div>
<div style="clear:both"></div>
`;
},
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;
},
}
});

15
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;
},
});

112
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 = `
<div class="<%= pfx %>assets-cont">
<div class="<%= pfx %>assets-header">
<form class="<%= pfx %>add-asset">
<div class="<%= ppfx %>field <%= pfx %>add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="<%= ppfx %>btn-prim"><%= btnText %></button>
<div style="clear:both"></div>
</form>
<div class="<%= pfx %>dips" style="display:none">
<button class="fa fa-th <%= ppfx %>btnt"></button>
<button class="fa fa-th-list <%= ppfx %>btnt"></button>
</div>
</div>
<div class="<%= pfx %>assets"></div>
<div style="clear:both"></div>
</div>
`;
module.exports = Backbone.View.extend({
template: _.template(assetsTemplate),
template(view) {
const pfx = view.pfx;
const ppfx = view.ppfx;
return `
<div class="${pfx}assets-cont">
<div class="${pfx}assets-header">
<form class="${pfx}add-asset">
<div class="${ppfx}field ${pfx}add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="${ppfx}btn-prim">${view.config.addBtnText}</button>
<div style="clear:both"></div>
</form>
<div class="${pfx}dips" style="display:none">
<button class="fa fa-th <%${ppfx}btnt"></button>
<button class="fa fa-th-list <%${ppfx}btnt"></button>
</div>
</div>
<div class="${pfx}assets" data-el="assets"></div>
<div style="clear:both"></div>
</div>
`;
},
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;
}
});

3
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();
},

36
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();

2
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

Loading…
Cancel
Save