Browse Source

Merge branch 'improve-sm-extend' into dev

pull/1518/head
Artur Arseniev 8 years ago
parent
commit
afd1dc3516
  1. 2
      dist/css/grapes.min.css
  2. 11
      src/domain_abstract/model/TypeableCollection.js
  3. 1
      src/domain_abstract/ui/Input.js
  4. 1
      src/domain_abstract/ui/InputNumber.js
  5. 9
      src/style_manager/model/Layer.js
  6. 9
      src/style_manager/model/Layers.js
  7. 317
      src/style_manager/model/Property.js
  8. 15
      src/style_manager/model/PropertyComposite.js
  9. 36
      src/style_manager/model/PropertyStack.js
  10. 12
      src/style_manager/view/LayerView.js
  11. 7
      src/style_manager/view/PropertyIntegerView.js
  12. 5
      src/style_manager/view/PropertySliderView.js
  13. 5
      src/style_manager/view/PropertyStackView.js
  14. 8
      src/style_manager/view/PropertyView.js
  15. 1
      src/styles/scss/_gjs_style_manager.scss
  16. 1
      test/specs/style_manager/index.js
  17. 4
      test/specs/style_manager/model/Models.js

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

11
src/domain_abstract/model/TypeableCollection.js

@ -1,3 +1,4 @@
import { isFunction } from 'underscore';
const Model = Backbone.Model; const Model = Backbone.Model;
const View = Backbone.View; const View = Backbone.View;
@ -112,8 +113,14 @@ export default {
const ModelInst = type ? type.model : baseType.model; const ModelInst = type ? type.model : baseType.model;
const ViewInst = type ? type.view : baseType.view; const ViewInst = type ? type.view : baseType.view;
let { model, view, isType } = definition; let { model, view, isType } = definition;
model = model instanceof Model ? model : ModelInst.extend(model || {}); model =
view = view instanceof View ? view : ViewInst.extend(view || {}); model instanceof Model || isFunction(model)
? model
: ModelInst.extend(model || {});
view =
view instanceof View || isFunction(view)
? view
: ViewInst.extend(view || {});
if (type) { if (type) {
type.model = model; type.model = model;

1
src/domain_abstract/ui/Input.js

@ -75,6 +75,7 @@ module.exports = Backbone.View.extend({
}, },
render() { render() {
this.inputEl = null;
const el = this.$el; const el = this.$el;
el.addClass(this.inputClass()); el.addClass(this.inputClass());
el.html(this.template()); el.html(this.template());

1
src/domain_abstract/ui/InputNumber.js

@ -269,6 +269,7 @@ module.exports = Input.extend({
render() { render() {
Input.prototype.render.call(this); Input.prototype.render.call(this);
this.unitEl = null;
const unit = this.getUnitEl(); const unit = this.getUnitEl();
unit && unit &&
this.$el this.$el

9
src/style_manager/model/Layer.js

@ -33,6 +33,15 @@ module.exports = Backbone.Model.extend({
} }
}, },
/**
* Get property at some index
* @param {Number} index
* @return {Object}
*/
getPropertyAt(index) {
return this.get('properties').at(index);
},
getPropertyValue(property) { getPropertyValue(property) {
let result = ''; let result = '';
this.get('properties').each(prop => { this.get('properties').each(prop => {

9
src/style_manager/model/Layers.js

@ -18,6 +18,11 @@ module.exports = Backbone.Collection.extend({
this.idx = 1; this.idx = 1;
}, },
getSeparator() {
const { property } = this;
return property ? property.get('layerSeparator') : ', ';
},
/** /**
* Get layers from a value string (for not detached properties), * Get layers from a value string (for not detached properties),
* example of input: * example of input:
@ -35,7 +40,7 @@ module.exports = Backbone.Collection.extend({
var cleaned = match.replace(/,\s*/g, ','); var cleaned = match.replace(/,\s*/g, ',');
value = value.replace(match, cleaned); value = value.replace(match, cleaned);
}); });
const layerValues = value ? value.split(', ') : []; const layerValues = value ? value.split(this.getSeparator()) : [];
layerValues.forEach(layerValue => { layerValues.forEach(layerValue => {
layers.push({ properties: this.properties.parseValue(layerValue) }); layers.push({ properties: this.properties.parseValue(layerValue) });
}); });
@ -101,7 +106,7 @@ module.exports = Backbone.Collection.extend({
getFullValue() { getFullValue() {
let result = []; let result = [];
this.each(layer => result.push(layer.getFullValue())); this.each(layer => result.push(layer.getFullValue()));
return result.join(', '); return result.join(this.getSeparator());
}, },
getPropertyValues(property) { getPropertyValues(property) {

317
src/style_manager/model/Property.js

@ -1,152 +1,181 @@
import { isUndefined, isString } from 'underscore'; import { isUndefined, isString } from 'underscore';
module.exports = require('backbone').Model.extend({ const Property = require('backbone').Model.extend(
defaults: { {
name: '', defaults: {
property: '', name: '',
type: '', property: '',
defaults: '', type: '',
info: '', defaults: '',
value: '', info: '',
icon: '', value: '',
functionName: '', icon: '',
status: '', functionName: '',
visible: true, status: '',
fixedValues: ['initial', 'inherit'], visible: true,
fixedValues: ['initial', 'inherit'],
// If true to the value will be added '!important'
important: 0, // If true, the property will be forced to be full width
full: 0,
// If true, will be hidden by default and will show up only for targets
// which require this property (via `stylable-require`) // If true to the value will be added '!important'
// Use case: important: 0,
// you can add all SVG CSS properties with toRequire as true
// and then require them on SVG Components // If true, will be hidden by default and will show up only for targets
toRequire: 0 // which require this property (via `stylable-require`)
}, // Use case:
// you can add all SVG CSS properties with toRequire as true
initialize(opt) { // and then require them on SVG Components
var o = opt || {}; toRequire: 0
var name = this.get('name'); },
var prop = this.get('property');
initialize(props = {}, opts = {}) {
if (!name) { var name = this.get('name');
this.set( var prop = this.get('property');
'name',
prop.charAt(0).toUpperCase() + prop.slice(1).replace(/-/g, ' ') if (!name) {
); this.set(
} 'name',
prop.charAt(0).toUpperCase() + prop.slice(1).replace(/-/g, ' ')
const init = this.init && this.init.bind(this); );
init && init(); }
}, Property.callInit(this, props, opts);
},
/**
* Clear the value init() {},
* @return {this}
*/ /**
clearValue(opts = {}) { * Clear the value
this.set({ value: undefined }, opts); * @return {this}
return this; */
}, clearValue(opts = {}) {
this.set({ value: undefined }, opts);
/** return this;
* Update value },
* @param {any} value
* @param {Boolen} [complete=true] Indicates if it's a final state /**
* @param {Object} [opts={}] Options * Update value
*/ * @param {any} value
setValue(value, complete = 1, opts = {}) { * @param {Boolen} [complete=true] Indicates if it's a final state
const parsed = this.parseValue(value); * @param {Object} [opts={}] Options
this.set(parsed, { ...opts, avoidStore: 1 }); */
setValue(value, complete = 1, opts = {}) {
// It's important to set an empty value, otherwise the const parsed = this.parseValue(value);
// UndoManager won't see the change this.set(parsed, { ...opts, avoidStore: 1 });
if (complete) {
this.set('value', '', opts); // It's important to set an empty value, otherwise the
this.set(parsed, opts); // UndoManager won't see the change
} if (complete) {
}, this.set('value', '', opts);
this.set(parsed, opts);
/** }
* Like `setValue` but, in addition, prevents the update of the input element },
* as the changes should come from the input itself.
* This method is useful with the definition of custom properties /**
* @param {any} value * Like `setValue` but, in addition, prevents the update of the input element
* @param {Boolen} [complete=true] Indicates if it's a final state * as the changes should come from the input itself.
* @param {Object} [opts={}] Options * This method is useful with the definition of custom properties
*/ * @param {any} value
setValueFromInput(value, complete, opts = {}) { * @param {Boolen} [complete=true] Indicates if it's a final state
this.setValue(value, complete, { ...opts, fromInput: 1 }); * @param {Object} [opts={}] Options
}, */
setValueFromInput(value, complete, opts = {}) {
this.setValue(value, complete, { ...opts, fromInput: 1 });
},
/**
* Parse a raw value, generally fetched from the target, for this property
* @param {string} value Raw value string
* @return {Object}
* @example
* // example with an Input type
* prop.parseValue('translateX(10deg)');
* // -> { value: 10, unit: 'deg', functionName: 'translateX' }
*
*/
parseValue(value, opts = {}) {
const result = { value };
const imp = '!important';
if (isString(value) && value.indexOf(imp) !== -1) {
result.value = value.replace(imp, '').trim();
result.important = 1;
}
if (!this.get('functionName') && !opts.complete) {
return result;
}
const args = [];
let valueStr = `${result.value}`;
let start = valueStr.indexOf('(') + 1;
let end = valueStr.lastIndexOf(')');
result.functionName = valueStr.substring(0, start - 1);
args.push(start);
// Will try even if the last closing parentheses is not found
if (end >= 0) {
args.push(end);
}
result.value = String.prototype.substring.apply(valueStr, args);
if (opts.numeric) {
const num = parseFloat(result.value);
result.unit = result.value.replace(num, '');
result.value = num;
}
/**
* Parse a raw value, generally fetched from the target, for this property
* @param {string} value Raw value string
* @return {Object}
* @example
* // example with an Input type
* prop.parseValue('translateX(10deg)');
* // -> { value: 10, unit: 'deg', functionName: 'translateX' }
*
*/
parseValue(value) {
const result = { value };
const imp = '!important';
if (isString(value) && value.indexOf(imp) !== -1) {
result.value = value.replace(imp, '').trim();
result.important = 1;
}
if (!this.get('functionName')) {
return result; return result;
},
/**
* Get the default value
* @return {string}
* @private
*/
getDefaultValue() {
return this.get('defaults');
},
/**
* Get a complete value of the property.
* This probably will replace the getValue when all
* properties models will be splitted
* @param {string} val Custom value to replace the one on the model
* @return {string}
* @private
*/
getFullValue(val) {
const fn = this.get('functionName');
let value = isUndefined(val) ? this.get('value') : val;
if (fn && !isUndefined(value)) {
value = `${fn}(${value})`;
}
if (this.get('important')) {
value = `${value} !important`;
}
return value || '';
} }
const args = [];
let valueStr = `${result.value}`;
let start = valueStr.indexOf('(') + 1;
let end = valueStr.lastIndexOf(')');
args.push(start);
// Will try even if the last closing parentheses is not found
if (end >= 0) {
args.push(end);
}
result.value = String.prototype.substring.apply(valueStr, args);
return result;
}, },
{
/** callParentInit(property, ctx, props, opts = {}) {
* Get the default value property.prototype.initialize.apply(ctx, [
* @return {string} props,
* @private {
*/ ...opts,
getDefaultValue() { skipInit: 1
return this.get('defaults'); }
}, ]);
},
/**
* Get a complete value of the property. callInit(context, props, opts = {}) {
* This probably will replace the getValue when all !opts.skipInit && context.init(props, opts);
* properties models will be splitted
* @param {string} val Custom value to replace the one on the model
* @return {string}
* @private
*/
getFullValue(val) {
const fn = this.get('functionName');
let value = isUndefined(val) ? this.get('value') : val;
if (fn && !isUndefined(value)) {
value = `${fn}(${value})`;
}
if (this.get('important')) {
value = `${value} !important`;
} }
return value || '';
} }
}); );
module.exports = Property;

15
src/style_manager/model/PropertyComposite.js

@ -21,11 +21,13 @@ module.exports = Property.extend({
separator: ' ' separator: ' '
}, },
init() { initialize(props = {}, opts = {}) {
Property.callParentInit(Property, this, props, opts);
const properties = this.get('properties') || []; const properties = this.get('properties') || [];
const Properties = require('./Properties'); const Properties = require('./Properties');
this.set('properties', new Properties(properties)); this.set('properties', new Properties(properties));
this.listenTo(this, 'change:value', this.updateValues); this.listenTo(this, 'change:value', this.updateValues);
Property.callInit(this, props, opts);
}, },
/** /**
@ -48,7 +50,7 @@ module.exports = Property.extend({
// 11px -> 11px 11px 11px 11xp // 11px -> 11px 11px 11px 11xp
// 11px 22px -> 11px 22px 11px 22xp // 11px 22px -> 11px 22px 11px 22xp
const value = const value =
values[i] || values[i % len + (len != 1 && len % 2 ? 1 : 0)]; values[i] || values[(i % len) + (len != 1 && len % 2 ? 1 : 0)];
// There some issue with UndoManager // There some issue with UndoManager
//property.setValue(value, 0, {fromParent: 1}); //property.setValue(value, 0, {fromParent: 1});
}); });
@ -78,5 +80,14 @@ module.exports = Property.extend({
} }
return this.get('properties').getFullValue(); return this.get('properties').getFullValue();
},
/**
* Get property at some index
* @param {Number} index
* @return {Object}
*/
getPropertyAt(index) {
return this.get('properties').at(index);
} }
}); });

36
src/style_manager/model/PropertyStack.js

@ -7,19 +7,51 @@ module.exports = Property.extend({
// Array of layers (which contain properties) // Array of layers (which contain properties)
layers: [], layers: [],
// The separator used to join layer values
layerSeparator: ', ',
// Layer preview // Layer preview
preview: 0 preview: 0
}, },
init() { initialize(props = {}, opts = {}) {
Property.prototype.init.apply(this, arguments); Property.callParentInit(Property, this, props, opts);
const layers = this.get('layers'); const layers = this.get('layers');
const layersColl = new Layers(layers); const layersColl = new Layers(layers);
layersColl.property = this;
layersColl.properties = this.get('properties'); layersColl.properties = this.get('properties');
this.set('layers', layersColl); this.set('layers', layersColl);
Property.callInit(this, props, opts);
},
getLayers() {
return this.get('layers');
},
getCurrentLayer() {
return this.getLayers().filter(layer => layer.get('active'))[0];
}, },
getFullValue() { getFullValue() {
return this.get('detached') ? '' : this.get('layers').getFullValue(); return this.get('detached') ? '' : this.get('layers').getFullValue();
},
/**
* This method allows to customize layers returned from the target
* @param {Object} target
* @return {Array} Should return an array of layers
* @example
* // return example
* [
* {
* properties: [
* { property: 'width', ... }
* { property: 'height', ... }
* ]
* }
* ]
*/
getLayersFromTarget(target) {
return;
} }
}); });

12
src/style_manager/view/LayerView.js

@ -41,10 +41,6 @@ module.exports = Backbone.View.extend({
this.listenTo(model, 'change:active', this.updateVisibility); this.listenTo(model, 'change:active', this.updateVisibility);
this.listenTo(model.get('properties'), 'change', this.updatePreview); this.listenTo(model.get('properties'), 'change', this.updatePreview);
if (!model.get('preview')) {
this.$el.addClass(this.pfx + 'no-preview');
}
// For the sorter // For the sorter
model.view = this; model.view = this;
model.set({ droppable: 0, draggable: 1 }); model.set({ droppable: 0, draggable: 1 });
@ -152,9 +148,8 @@ module.exports = Backbone.View.extend({
render() { render() {
const PropertiesView = require('./PropertiesView'); const PropertiesView = require('./PropertiesView');
const propsConfig = this.propsConfig; const propsConfig = this.propsConfig;
const className = `${this.pfx}layer`; const { model, el, pfx } = this;
const model = this.model; const preview = model.get('preview');
const el = this.el;
const properties = new PropertiesView({ const properties = new PropertiesView({
collection: model.get('properties'), collection: model.get('properties'),
config: this.config, config: this.config,
@ -163,8 +158,9 @@ module.exports = Backbone.View.extend({
propTarget: propsConfig.propTarget, propTarget: propsConfig.propTarget,
onChange: propsConfig.onChange onChange: propsConfig.onChange
}).render().el; }).render().el;
el.innerHTML = this.template(model); el.innerHTML = this.template(model);
el.className = className; el.className = `${pfx}layer${!preview ? ` ${pfx}no-preview` : ''}`;
this.getPropertiesWrapper().appendChild(properties); this.getPropertiesWrapper().appendChild(properties);
this.updateVisibility(); this.updateVisibility();
this.updatePreview(); this.updatePreview();

7
src/style_manager/view/PropertyIntegerView.js

@ -13,6 +13,7 @@ module.exports = PropertyView.extend({
const model = this.model; const model = this.model;
this.listenTo(model, 'change:unit', this.modelValueChanged); this.listenTo(model, 'change:unit', this.modelValueChanged);
this.listenTo(model, 'el:change', this.elementUpdated); this.listenTo(model, 'el:change', this.elementUpdated);
this.listenTo(model, 'change:units', this.render);
}, },
setValue(value) { setValue(value) {
@ -36,5 +37,11 @@ module.exports = PropertyView.extend({
this.input = this.$input.get(0); this.input = this.$input.get(0);
this.inputInst = input; this.inputInst = input;
} }
},
clearCached() {
PropertyView.prototype.clearCached.apply(this, arguments);
this.unit = null;
this.$unit = null;
} }
}); });

5
src/style_manager/view/PropertySliderView.js

@ -46,8 +46,9 @@ module.exports = Property.extend({
}, },
setValue(value) { setValue(value) {
this.getSliderEl().value = parseFloat(value); const parsed = this.model.parseValue(value);
this.inputInst.setValue(value, { silent: 1 }); this.getSliderEl().value = parseFloat(parsed.value);
Property.prototype.setValue.apply(this, arguments);
}, },
onRender() { onRender() {

5
src/style_manager/view/PropertyStackView.js

@ -117,10 +117,10 @@ module.exports = PropertyCompositeView.extend({
const model = this.model; const model = this.model;
const layers = this.getLayers(); const layers = this.getLayers();
const detached = model.get('detached'); const detached = model.get('detached');
const target = this.getTarget();
// With detached layers values will be assigned to their properties // With detached layers values will be assigned to their properties
if (detached) { if (detached) {
const target = this.getTarget();
const style = target ? target.getStyle() : {}; const style = target ? target.getStyle() : {};
layersObj = layers.getLayersFromStyle(style); layersObj = layers.getLayersFromStyle(style);
} else { } else {
@ -129,8 +129,9 @@ module.exports = PropertyCompositeView.extend({
layersObj = layers.getLayersFromValue(value); layersObj = layers.getLayersFromValue(value);
} }
const toAdd = model.getLayersFromTarget(target) || layersObj;
layers.reset(); layers.reset();
layers.add(layersObj); layers.add(toAdd);
model.set({ stackIndex: null }, { silent: true }); model.set({ stackIndex: null }, { silent: true });
}, },

8
src/style_manager/view/PropertyView.js

@ -484,8 +484,14 @@ module.exports = Backbone.View.extend({
const pfx = this.pfx; const pfx = this.pfx;
const model = this.model; const model = this.model;
const el = this.el; const el = this.el;
const property = model.get('property');
const full = model.get('full');
const className = `${pfx}property`;
el.innerHTML = this.template(model); el.innerHTML = this.template(model);
el.className = `${pfx}property ${pfx}${model.get('type')}`; el.className = `${className} ${pfx}${model.get(
'type'
)} ${className}__${property}`;
el.className += full ? ` ${className}--full` : '';
this.updateStatus(); this.updateStatus();
const onRender = this.onRender && this.onRender.bind(this); const onRender = this.onRender && this.onRender.bind(this);

1
src/styles/scss/_gjs_style_manager.scss

@ -252,6 +252,7 @@
margin-bottom: 5px; margin-bottom: 5px;
padding: 0 5px; padding: 0 5px;
&--full,
&.#{$sm-prefix}composite, &.#{$sm-prefix}composite,
&.#{$sm-prefix}file, &.#{$sm-prefix}file,
&.#{$sm-prefix}list, &.#{$sm-prefix}list,

1
test/specs/style_manager/index.js

@ -155,6 +155,7 @@ describe('StyleManager', () => {
} }
] ]
}); });
obj.onLoad();
}); });
afterEach(() => { afterEach(() => {

4
test/specs/style_manager/model/Models.js

@ -155,7 +155,7 @@ module.exports = {
test('parseValue with function and functionName', () => { test('parseValue with function and functionName', () => {
obj = new Property({ functionName: 'fn' }); obj = new Property({ functionName: 'fn' });
const result = { value: 'testValue' }; const result = { value: 'testValue', functionName: 'fn' };
expect(obj.parseValue('fn(testValue)')).toEqual(result); expect(obj.parseValue('fn(testValue)')).toEqual(result);
expect(obj.parseValue('fn(testValue')).toEqual(result); expect(obj.parseValue('fn(testValue')).toEqual(result);
}); });
@ -194,7 +194,7 @@ module.exports = {
units: ['px', 'deg'], units: ['px', 'deg'],
functionName: 'test' functionName: 'test'
}); });
const result = { value: 55, unit: 'deg' }; const result = { value: 55, unit: 'deg', functionName: 'test' };
expect(obj.parseValue('test(55deg)')).toEqual(result); expect(obj.parseValue('test(55deg)')).toEqual(result);
}); });

Loading…
Cancel
Save