diff --git a/src/style_manager/view/LayerView.js b/src/style_manager/view/LayerView.js
index 4edbbabc2..707bd9598 100644
--- a/src/style_manager/view/LayerView.js
+++ b/src/style_manager/view/LayerView.js
@@ -1,24 +1,21 @@
-import { isString, each } from 'underscore';
-import Backbone from 'backbone';
-import PropertiesView from './PropertiesView';
+import { View } from 'backbone';
-export default Backbone.View.extend({
+export default View.extend({
events: {
click: 'active',
'click [data-close-layer]': 'removeItem',
'mousedown [data-move-layer]': 'initSorter',
- 'touchstart [data-move-layer]': 'initSorter'
+ 'touchstart [data-move-layer]': 'initSorter',
},
- template(model) {
- const { pfx, ppfx, em } = this;
- const label = `${em && em.t('styleManager.layer')} ${model.get('index')}`;
+ template() {
+ const { pfx, ppfx } = this;
return `
- ${label}
+
@@ -31,18 +28,19 @@ export default Backbone.View.extend({
},
initialize(o = {}) {
- let model = this.model;
+ const { model } = this;
this.stackModel = o.stackModel;
+ this.propertyView = o.propertyView;
this.config = o.config || {};
this.em = this.config.em;
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.sorter = o.sorter || null;
this.propsConfig = o.propsConfig || {};
- this.customPreview = o.onPreview;
+ this.pModel = this.propertyView.model;
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:active', this.updateVisibility);
- this.listenTo(model.get('properties'), 'change', this.updatePreview);
+ this.listenTo(model, 'change:values', this.updateLabel);
// For the sorter
model.view = this;
@@ -63,76 +61,9 @@ export default Backbone.View.extend({
this.remove();
},
- remove(opts = {}) {
- const { model, props } = this;
- const coll = model.collection;
- const stackModel = this.stackModel;
-
- Backbone.View.prototype.remove.apply(this, arguments);
- coll && coll.contains(model) && coll.remove(model);
-
- if (stackModel && stackModel.set) {
- stackModel.set({ stackIndex: null }, { silent: true });
- !opts.fromTarget && stackModel.trigger('updateValue');
- }
-
- props && props.remove();
- },
-
- /**
- * Default method for changing preview box
- * @param {Collection} props
- * @param {Element} $el
- */
- onPreview(value) {
- const { stackModel } = this;
- const detach = stackModel && stackModel.get('detached');
- const values = value.split(' ');
- const lim = 3;
- const result = [];
- const resultObj = {};
-
- this.model.get('properties').each((prop, index) => {
- const property = prop.get('property');
- let value = detach ? prop.getFullValue() : values[index] || '';
-
- if (value) {
- if (prop.get('type') == 'integer') {
- let valueInt = parseInt(value, 10);
- let unit = value.replace(valueInt, '');
- valueInt = !isNaN(valueInt) ? valueInt : 0;
- valueInt = valueInt > lim ? lim : valueInt;
- valueInt = valueInt < -lim ? -lim : valueInt;
- value = valueInt + unit;
- }
- }
-
- result.push(value);
- resultObj[property] = value;
- });
-
- return detach ? resultObj : result.join(' ');
- },
-
- updatePreview() {
- const stackModel = this.stackModel;
- const customPreview = this.customPreview;
- const previewEl = this.getPreviewEl();
- const value = this.model.getFullValue();
- const preview = customPreview
- ? customPreview(value)
- : this.onPreview(value);
-
- if (preview && stackModel && previewEl) {
- const { style } = previewEl;
- if (isString(preview)) {
- style[stackModel.get('property')] = preview;
- } else {
- let prvStr = [];
- each(preview, (val, prop) => prvStr.push(`${prop}:${val}`));
- previewEl.setAttribute('style', prvStr.join(';'));
- }
- }
+ remove() {
+ this.pModel.removeLayer(this.model);
+ View.prototype.remove.apply(this, arguments);
},
getPropertiesWrapper() {
@@ -149,40 +80,43 @@ export default Backbone.View.extend({
return this.previewEl;
},
+ getLabelEl() {
+ if (!this.labelEl) {
+ this.labelEl = this.el.querySelector('[data-label]');
+ }
+ return this.labelEl;
+ },
+
active() {
- const model = this.model;
- const collection = model.collection;
- collection.active(collection.indexOf(model));
+ const { model, propertyView } = this;
+ const pm = propertyView.model;
+ if (pm.getSelectedLayer() === model) return;
+ pm.selectLayer(model);
+ model.collection.active(model.getIndex());
},
updateVisibility() {
- const pfx = this.pfx;
+ const { pfx, model, propertyView } = this;
const wrapEl = this.getPropertiesWrapper();
- const active = this.model.get('active');
+ const active = model.get('active');
wrapEl.style.display = active ? '' : 'none';
this.$el[active ? 'addClass' : 'removeClass'](`${pfx}active`);
+ active && wrapEl.appendChild(propertyView.props.el);
+ },
+
+ updateLabel() {
+ const { model, propertyView } = this;
+ const label = propertyView.model.getLayerLabel(model);
+ this.getLabelEl().innerHTML = label;
},
render() {
- const propsConfig = this.propsConfig;
const { model, el, pfx } = this;
const preview = model.get('preview');
- const properties = new PropertiesView({
- collection: model.get('properties'),
- config: { ...this.config, fromLayer: 1 },
- target: propsConfig.target,
- customValue: propsConfig.customValue,
- propTarget: propsConfig.propTarget,
- onChange: propsConfig.onChange
- });
- const propsEl = properties.render().el;
-
- el.innerHTML = this.template(model);
+ el.innerHTML = this.template();
el.className = `${pfx}layer${!preview ? ` ${pfx}no-preview` : ''}`;
- this.props = properties;
- this.getPropertiesWrapper().appendChild(propsEl);
+ this.updateLabel();
this.updateVisibility();
- this.updatePreview();
return this;
- }
+ },
});
diff --git a/src/style_manager/view/LayersView.js b/src/style_manager/view/LayersView.js
index 17935deef..9d33ed41f 100644
--- a/src/style_manager/view/LayersView.js
+++ b/src/style_manager/view/LayersView.js
@@ -5,6 +5,7 @@ export default Backbone.View.extend({
initialize(o) {
this.config = o.config || {};
this.stackModel = o.stackModel;
+ this.propertyView = o.propertyView;
this.preview = o.preview;
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
@@ -27,7 +28,7 @@ export default Backbone.View.extend({
ignoreViewChildren: 1,
containerSel: `.${pfx}layers`,
itemSel: `.${pfx}layer`,
- pfx: this.config.pStylePrefix
+ pfx: this.config.pStylePrefix,
})
: '';
@@ -58,8 +59,8 @@ export default Backbone.View.extend({
* */
addToCollection(model, fragmentEl, index) {
var fragment = fragmentEl || null;
+ const { propertyView, config } = this;
const stackModel = this.stackModel;
- const config = this.config;
const sorter = this.sorter;
const propsConfig = this.propsConfig;
@@ -72,7 +73,8 @@ export default Backbone.View.extend({
config,
sorter,
stackModel,
- propsConfig
+ propsConfig,
+ propertyView,
});
const rendered = view.render().el;
this.items.push(view);
@@ -91,11 +93,7 @@ export default Backbone.View.extend({
// In case the added is new in the collection index will be -1
if (index < 0) {
this.$el.append(rendered);
- } else
- this.$el
- .children()
- .eq(index)
- [method](rendered);
+ } else this.$el.children().eq(index)[method](rendered);
} else this.$el.append(rendered);
}
@@ -120,7 +118,7 @@ export default Backbone.View.extend({
var fragment = document.createDocumentFragment();
this.$el.empty();
- this.collection.each(function(model) {
+ this.collection.each(function (model) {
this.addToCollection(model, fragment);
}, this);
@@ -140,5 +138,5 @@ export default Backbone.View.extend({
clearItems(opts) {
this.items.forEach(item => item.remove(opts));
this.items = [];
- }
+ },
});
diff --git a/src/style_manager/view/PropertyStackView.js b/src/style_manager/view/PropertyStackView.js
index 446f7140a..2a6aad4a0 100644
--- a/src/style_manager/view/PropertyStackView.js
+++ b/src/style_manager/view/PropertyStackView.js
@@ -1,14 +1,18 @@
-import { isUndefined, keys } from 'underscore';
import PropertyCompositeView from './PropertyCompositeView';
+import PropertiesView from './PropertiesView';
import LayersView from './LayersView';
-import CssGenerator from 'code_manager/model/CssGenerator';
-
-const cssGen = new CssGenerator();
export default PropertyCompositeView.extend({
+ events() {
+ return {
+ ...PropertyCompositeView.prototype.events,
+ 'click [data-add-layer]': 'addLayer',
+ change: '',
+ };
+ },
+
templateInput() {
- const pfx = this.pfx;
- const ppfx = this.ppfx;
+ const { pfx } = this;
return `
@@ -17,109 +21,10 @@ export default PropertyCompositeView.extend({
`;
},
- init() {
- const model = this.model;
- const pfx = this.pfx;
- model.set('stackIndex', null);
- this.events[`click [data-add-layer]`] = 'addLayer';
- this.listenTo(model, 'change:stackIndex', this.indexChanged);
- this.listenTo(model, 'updateValue', this.inputValueChanged);
- this.delegateEvents();
-
- const propsConfig = this.getPropsConfig();
- this.layers = new LayersView({
- collection: this.getLayers(),
- stackModel: model,
- preview: model.get('preview'),
- config: this.config,
- propsConfig
- });
- // For detached properties, used in inputValueChanged (eg. clear all)
- const PropertiesView = require('./PropertiesView').default;
- this.propsView = new PropertiesView({
- target: this.target,
- collection: model.get('properties'),
- stackModel: model,
- config: this.config,
- onChange: propsConfig.onChange,
- propTarget: propsConfig.propTarget
- });
- },
-
- /**
- * Fired when the target is updated.
- * With detached mode the component will be always empty as its value
- * so we gonna check all props and find if it has any difference
- * */
- targetUpdated(...args) {
- let data;
- if (!this.model.get('detached')) {
- data = PropertyCompositeView.prototype.targetUpdated.apply(this, args);
- } else {
- data = this._getTargetData();
- this.setStatus(data.status);
- this.checkVisibility();
- }
-
- // I have to wait the update of inner properites (like visibility)
- // before render layers
- setTimeout(() => this.refreshLayers(data));
- },
-
- /**
- * Returns the collection of layers
- * @return {Collection}
- */
- getLayers() {
- return this.model.get('layers');
- },
-
- /**
- * Triggered when another layer has been selected.
- * This allow to move all rendered properties to a new
- * selected layer
- * @param {Event}
- *
- * @return {Object}
- * */
- indexChanged(e) {
- const model = this.model;
- this.getLayers().active(model.get('stackIndex'));
- },
+ init() {},
addLayer() {
- const model = this.model;
- const layers = this.getLayers();
- const prepend = model.get('prepend');
- const properties = model.get('properties').deepClone();
- properties.each(property => property.set('value', ''));
- const layer = layers.add(
- { properties },
- {
- active: 1,
- ...(prepend && { at: 0 })
- }
- );
-
- // In detached mode inputValueChanged will add new 'layer value'
- // to all subprops
- this.inputValueChanged({ up: 1 });
-
- // This will set subprops with a new default values
- model.set('stackIndex', layers.indexOf(layer));
- },
-
- inputValueChanged(opts = {}) {
- const model = this.model;
- opts.up && this.elementUpdated();
-
- // If not detached I'll just put all the values from layers to property
- // eg. background: layer1Value, layer2Value, layer3Value, ...
- if (!model.get('detached')) {
- model.set('value', this.getLayerValues());
- } else {
- model.get('properties').each(prop => prop.trigger('change:value'));
- }
+ this.model.addLayer({}, { at: 0 });
},
/**
@@ -129,232 +34,43 @@ export default PropertyCompositeView.extend({
*/
setValue() {},
- /**
- * Create value by layers
- * @return string
- * */
- getLayerValues() {
- return this.getLayers().getFullValue();
- },
-
- _getClassRule(opts = {}) {
- const { em } = this;
- const { skipAdd = 1 } = opts;
- const selected = em.getSelected();
- const targetAlt = em.get('StyleManager').getModelToStyle(selected, {
- skipAdd,
- useClasses: 1
- });
- return targetAlt !== selected && targetAlt;
- },
-
- /**
- * Return the parent style rule of the passed one
- * @private
- */
- _getParentTarget(target, opts = {}) {
- const { em, model } = this;
- const property = model.get('property');
- const isValid = opts.isValid || (rule => rule.getStyle()[property]);
- const targetsDevice = em
- .get('CssComposer')
- .getAll()
- .filter(rule => rule.selectorsToString() === target.getSelectorsString());
- const map = targetsDevice.reduce((acc, rule) => {
- acc[rule.getAtRule()] = rule;
- return acc;
- }, {});
- const mapSorted = cssGen.sortMediaObject(map);
- const sortedRules = mapSorted.map(item => item.value);
- const currIndex = sortedRules.indexOf(target);
- const rulesToCheck = sortedRules.splice(0, currIndex);
- let result;
-
- for (let i = rulesToCheck.length - 1; i > -1; i--) {
- const rule = rulesToCheck[i];
- if (isValid(rule)) {
- // only for not detached
- result = rule;
- break;
- }
- }
-
- return result;
+ remove() {
+ this.layersView?.remove();
+ PropertyCompositeView.prototype.remove.apply(this, arguments);
},
- /**
- * Refresh layers
- * */
- refreshLayers(opts = {}) {
- let layersObj = [];
- const { model, em } = this;
- const layers = this.getLayers();
- const detached = model.get('detached');
- const property = model.get('property');
- const target = this.getFirstTarget();
- const valueComput = this.getComputedValue();
- const selected = em.getSelected();
- const updateOpts = { fromTarget: 1 };
- let resultValue,
- style,
- targetAlt,
- targetAltDevice,
- valueTargetAlt,
- valueTrgAltDvc;
-
- // With detached layers values will be assigned to their properties
- if (detached) {
- style = opts.targetValue || {};
- const hasDetachedStyle = rule => {
- const name = model
- .get('properties')
- .at(0)
- .get('property');
- return rule && !isUndefined(rule.getStyle()[name]);
- };
-
- // If the style object is empty but the target has a computed value,
- // that means the style might exist in some other place
- if (!keys(style).length && valueComput && selected) {
- // Styles of the same target but with a higher rule
- const parentOpts = { isValid: rule => hasDetachedStyle(rule) };
- targetAltDevice = this._getParentTarget(target, parentOpts);
-
- if (targetAltDevice) {
- style = targetAltDevice.getStyle();
- } else {
- // The target is a component but the style is in the class rules
- targetAlt = this._getClassRule();
- valueTargetAlt = hasDetachedStyle(targetAlt) && targetAlt.getStyle();
- targetAltDevice =
- !valueTargetAlt &&
- this._getParentTarget(
- this._getClassRule({ skipAdd: 0 }),
- parentOpts
- );
- valueTrgAltDvc =
- hasDetachedStyle(targetAltDevice) && targetAltDevice.getStyle();
- style = valueTargetAlt || valueTrgAltDvc || {};
- }
- }
-
- resultValue = style;
- layersObj = layers.getLayersFromStyle(style);
- } else {
- const valueTrg = this.getTargetValue({ ignoreDefault: 1 });
- let value = valueTrg;
-
- // Try to check if the style is in another rule
- if (!value && valueComput) {
- // Styles of the same target but with a higher rule
- targetAltDevice = this._getParentTarget(target);
-
- if (targetAltDevice) {
- value = targetAltDevice.getStyle()[property];
- } else {
- // Computed value is not always reliable due to the browser's CSSOM parser
- // here we try to look for the style in class rules
- targetAlt = this._getClassRule();
- valueTargetAlt = targetAlt && targetAlt.getStyle()[property];
- targetAltDevice =
- !valueTargetAlt &&
- this._getParentTarget(this._getClassRule({ skipAdd: 0 }));
- valueTrgAltDvc =
- targetAltDevice && targetAltDevice.getStyle()[property];
- value = valueTargetAlt || valueTrgAltDvc || valueComput;
- }
- }
-
- value = value == model.getDefaultValue() ? '' : value;
- resultValue = value;
- layersObj = layers.getLayersFromValue(value);
- }
-
- const toAdd =
- model.getLayersFromTarget(target, { resultValue, layersObj }) ||
- layersObj;
- layers.reset(null, updateOpts);
- layers.add(toAdd, updateOpts);
- model.set({ stackIndex: null }, { silent: true });
+ clearCached() {
+ PropertyCompositeView.prototype.clearCached.apply(this, arguments);
+ this.layersView = null;
},
- getTargetValue(opts = {}) {
- const { model } = this;
- const { detached } = model.attributes;
- const target = this.getFirstTarget();
- let result = PropertyCompositeView.prototype.getTargetValue.call(
- this,
- opts
- );
-
- // It might happen that the browser split properties on CSSOM parse
- if (isUndefined(result) && !detached) {
- result = model.getValueFromStyle(target.getStyle());
- } else if (detached) {
- result = model.getValueFromTarget(target);
+ onRender() {
+ const { model, el } = this;
+ const props = model.getProperties();
+
+ if (props.length && !this.props) {
+ const propsView = new PropertiesView({
+ config: {
+ ...this.config,
+ highlightComputed: false,
+ highlightChanged: false,
+ },
+ collection: props,
+ parent: this,
+ });
+ propsView.render();
+
+ const layersView = new LayersView({
+ collection: model.getLayers(),
+ config: this.config,
+ propertyView: this,
+ });
+ layersView.render();
+
+ const fieldEl = el.querySelector('[data-layers-wrapper]');
+ fieldEl.appendChild(layersView.el);
+ this.props = propsView;
+ this.layersView = layersView;
}
-
- return result;
- },
-
- getPropsConfig() {
- const self = this;
- const { model } = self;
-
- return {
- target: self.target,
- propTarget: self.propTarget,
-
- // Things to do when a single sub-property is changed
- onChange(el, view, opt) {
- const subModel = view.model;
- const status = model.get('status');
-
- if (model.get('detached')) {
- const subProp = subModel.get('property');
- const defVal = subModel.getDefaultValue();
- const layers = self.getLayers();
- const values = layers.getPropertyValues(subProp, defVal);
- view.updateTargetStyle(values, null, opt);
- // Update also the target with values of special hidden properties.
- // This fixes the case of update with computed layers
- if (
- subProp == 'background-image' &&
- !opt.avoidStore &&
- status == 'computed'
- ) {
- model
- .get('properties')
- .filter(prop => prop.get('property').substr(0, 2) == '__')
- .forEach(prop => {
- const name = prop.get('property');
- const value = layers.getPropertyValues(
- name,
- prop.getDefaultValue()
- );
- self
- .getTargets()
- .forEach(tr => tr.addStyle({ [name]: value }, opt));
- });
- }
- } else {
- // Update only if there is an actual update (to avoid changes for computed styles)
- // ps: status is calculated in `targetUpdated` method
- if (status == 'updated') {
- const value = model.getFullValue();
- model.set('value', value, opt);
- // Try to remove detached properties
- !value && view.updateTargetStyle(value, null, opt);
- }
- }
- }
- };
},
-
- onRender() {
- const { el, layers, propsView } = this;
- const fieldEl = el.querySelector('[data-layers-wrapper]');
- propsView.render(); // Will use it to propogate changes
- fieldEl.appendChild(layers.render().el);
- }
});