Free and Open source Web Builder Framework. Next generation tool for building templates without coding
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

298 lines
7.6 KiB

import { keys, isUndefined } from 'underscore';
import Property from './PropertyComposite';
import PropertyBase from './Property';
import Layers from './Layers';
import { camelCase } from 'utils/mixins';
const VALUES_REG = /,(?![^\(]*\))/;
const PARTS_REG = /\s(?![^(]*\))/;
export default Property.extend({
defaults: {
...Property.prototype.defaults,
// Array of layers (which contain properties)
layers: [],
// The separator used to join layer values
layerSeparator: ', ',
// Prepend new layers in the list
prepend: 0,
// Layer preview
preview: 0,
// Parse single layer value string
parseLayer: null,
// Current selected layer
selectedLayer: null,
},
initialize(props = {}, opts = {}) {
Property.callParentInit(Property, this, props, opts);
const layers = this.get('layers');
const layersColl = new Layers(layers);
layersColl.property = this;
layersColl.properties = this.get('properties');
this.set('layers', layersColl, { silent: true });
this.on('change:selectedLayer', this.__upSelected);
this.listenTo(layersColl, 'add remove', this.__upLayers);
Property.callInit(this, props, opts);
},
__upProperties(prop, opts = {}) {
const layer = this.getSelectedLayer();
if (opts.__up || !layer) return;
const name = prop.getName();
layer.upValues({ [name]: prop.__getFullValue() });
const value = this.__getFullValue();
this.upValue(value, opts);
},
__upTargets(p, opts = {}) {
if (opts.__select) return;
return PropertyBase.prototype.__upTargets.call(this, p, opts);
},
__upTargetsStyle(style, opts) {
return PropertyBase.prototype.__upTargetsStyle.call(this, style, opts);
},
__upLayers(m, c, o) {
const value = this.__getFullValue();
this.upValue(value);
},
__upSelected({ noEvent } = {}, opts = {}) {
if (!this.__hasCustom()) return;
const sm = this.em.get('StyleManager');
const selected = this.getSelectedLayer();
const values = selected?.getValues();
// Update properties by layer value
values &&
this.getProperties().forEach(prop => {
const name = prop.getName();
const value = values[name];
!isUndefined(value) && prop.upValue(value, { ...opts, __up: true });
});
!noEvent && sm.__trgEv(sm.events.layerSelect, { property: this });
},
_up(props, opts = {}) {
const { __layers = [], ...rest } = props;
const layers = this.getLayers();
const layersNew = __layers.map(values => ({ values }));
if (layers.length === layersNew.length) {
layersNew.map((layer, n) => {
layers.at(n)?.upValues(layer.values);
});
} else {
this.getLayers().reset(layersNew);
}
this.__upSelected({ noEvent: true }, opts);
return Property.prototype._up.call(this, rest, opts);
},
__parseValue(value) {
const result = this.parseValue(value);
result.__layers = value
.split(VALUES_REG)
.map(v => v.trim())
.map(v => this.__parseLayer(v))
.filter(Boolean);
return result;
},
__parseLayer(value) {
const parseFn = this.get('parseLayer');
const values = value.split(PARTS_REG);
const properties = this.getProperties();
return parseFn
? parseFn({ value, values })
: properties.reduce((acc, prop, i) => {
const value = values[i];
acc[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue();
return acc;
}, {});
},
__getFromStyle(style = {}) {
const fromStyle = this.get('fromStyle');
return fromStyle ? fromStyle(style) : style;
},
hasValue(opts) {
return PropertyBase.prototype.hasValue.call(this, opts);
},
/**
* Add new layer to the stack
* @param {Object} [props={}] Layer props
* @param {Object} [opts={}] Options
* @returns {[Layer]}
*/
addLayer(props = {}, opts = {}) {
const values = {};
this.getProperties().forEach(prop => {
const name = prop.getName();
const value = props[name];
values[name] = isUndefined(value) ? prop.getDefaultValue() : value;
});
const layer = this.get('layers').push({ values }, opts);
return layer;
},
/**
* Remove layer
* @param {[Layer]} layer
* @returns {[Layer]} Removed layer
*/
removeLayer(layer) {
return this.get('layers').remove(layer);
},
/**
* Remove layer at index
* @param {Number} index Index of the layer to remove
* @returns {[Layer] | null} Removed layer
*/
removeLayerAt(index = 0) {
const layer = this.getLayer(index);
return layer ? this.removeLayer(layer) : null;
},
/**
* Select layer
* @param {[Layer]} layer
*/
selectLayer(layer) {
return this.set('selectedLayer', layer, { __select: true });
},
/**
* Select layer at index
* @param {Number} index Index of the layer to select
*/
selectLayerAt(index = 0) {
const layer = this.getLayer(index);
return layer && this.selectLayer(layer);
},
/**
* Get selected layer
* @returns {[Layer] | null}
*/
getSelectedLayer() {
const layer = this.get('selectedLayer');
return layer && layer.getIndex() >= 0 ? layer : null;
},
/**
* Get style object from layer values
* @param {[Layer]} layer
*/
getStyleFromLayer(layer, opts = {}) {
const sep = this.get('separator');
const values = layer.getValues();
const result = this.getProperties().map(prop => {
const name = prop.getName();
const val = values[name];
const value = isUndefined(val) ? prop.getDefaultValue() : val;
return { name, value };
});
const style = this.get('detached')
? result.reduce((acc, item) => {
acc[item.name] = item.value;
return acc;
}, {})
: {
[this.getName()]: result.map(r => r.value).join(sep),
};
return opts.camelCase
? Object.keys(style).reduce((res, key) => {
res[camelCase(key)] = style[key];
return res;
}, {})
: style;
},
__getFullValue() {
if (this.get('detached')) return '';
const name = this.getName();
return this.getLayers()
.map(l => this.getStyleFromLayer(l))
.map(s => s[name])
.filter(Boolean)
.map(v => v?.trim())
.join(this.get('layerSeparator'));
},
getLayers() {
return this.get('layers');
},
getLayer(index = 0) {
return this.getLayers().at(index) || null;
},
getCurrentLayer() {
return this.getLayers().filter(layer => layer.get('active'))[0];
},
getFullValue() {
return this.get('detached') ? '' : this.get('layers').getFullValue();
},
getValueFromStyle(styles = {}) {
const layers = this.getLayers().getLayersFromStyle(styles);
return new Layers(layers).getFullValue();
},
clearValue() {
this.getLayers().reset();
return Property.prototype.clearValue.apply(this, arguments);
},
getValueFromTarget(target) {
const { detached, property, properties } = this.attributes;
const style = target.getStyle();
const validStyles = {};
properties.forEach(prop => {
const name = prop.get('property');
const value = style[name];
if (value) validStyles[name] = value;
});
return !detached ? style[property] : keys(validStyles).length ? validStyles : '';
},
/**
* 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;
},
});