From 0b25829a511d6124f8225e70ea7ba14f727bb0c9 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Fri, 31 Dec 2021 11:44:16 +0100 Subject: [PATCH] Start updating stack view --- src/style_manager/view/LayerView.js | 140 ++------ src/style_manager/view/LayersView.js | 18 +- src/style_manager/view/PropertyStackView.js | 374 +++----------------- 3 files changed, 90 insertions(+), 442 deletions(-) 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); - } });