mirror of https://github.com/artf/grapesjs.git
1 changed files with 215 additions and 218 deletions
@ -1,234 +1,231 @@ |
|||||
import Backbone from 'backbone'; |
import { Model } from 'common'; |
||||
import { isUndefined, isString } from 'underscore'; |
import { isUndefined, isString } from 'underscore'; |
||||
import { capitalize } from 'utils/mixins'; |
import { capitalize } from 'utils/mixins'; |
||||
|
|
||||
const Property = Backbone.Model.extend( |
export default class Property extends Model { |
||||
{ |
initialize(props = {}, opts = {}) { |
||||
defaults: { |
const id = this.get('id') || ''; |
||||
name: '', |
const name = this.get('name') || ''; |
||||
property: '', |
!this.get('property') && |
||||
type: '', |
this.set('property', (name || id).replace(/ /g, '-')); |
||||
defaults: '', |
const prop = this.get('property'); |
||||
info: '', |
!this.get('id') && this.set('id', prop); |
||||
value: '', |
!name && this.set('name', capitalize(prop).replace(/-/g, ' ')); |
||||
icon: '', |
Property.callInit(this, props, opts); |
||||
functionName: '', |
} |
||||
status: '', |
|
||||
visible: true, |
|
||||
fixedValues: ['initial', 'inherit'], |
|
||||
|
|
||||
// If true, the property will be forced to be full width
|
|
||||
full: 0, |
|
||||
|
|
||||
// If true to the value will be added '!important'
|
|
||||
important: 0, |
|
||||
|
|
||||
// If true, will be hidden by default and will show up only for targets
|
|
||||
// which require this property (via `stylable-require`)
|
|
||||
// Use case:
|
|
||||
// you can add all SVG CSS properties with toRequire as true
|
|
||||
// and then require them on SVG Components
|
|
||||
toRequire: 0, |
|
||||
|
|
||||
// Specifies dependency on other properties of the selected object.
|
|
||||
// Property is shown only when all conditions are matched.
|
|
||||
//
|
|
||||
// example: { display: ['flex', 'block'], position: ['absolute'] };
|
|
||||
// in this case the property is only shown when display is
|
|
||||
// of value 'flex' or 'block' AND position is 'absolute'
|
|
||||
requires: null, |
|
||||
|
|
||||
// Specifies dependency on properties of the parent of the selected object.
|
|
||||
// Property is shown only when all conditions are matched.
|
|
||||
requiresParent: null |
|
||||
}, |
|
||||
|
|
||||
initialize(props = {}, opts = {}) { |
|
||||
const id = this.get('id') || ''; |
|
||||
const name = this.get('name') || ''; |
|
||||
!this.get('property') && |
|
||||
this.set('property', (name || id).replace(/ /g, '-')); |
|
||||
const prop = this.get('property'); |
|
||||
!this.get('id') && this.set('id', prop); |
|
||||
!name && this.set('name', capitalize(prop).replace(/-/g, ' ')); |
|
||||
Property.callInit(this, props, opts); |
|
||||
}, |
|
||||
|
|
||||
init() {}, |
|
||||
|
|
||||
/** |
|
||||
* Clear the value |
|
||||
* @return {this} |
|
||||
*/ |
|
||||
clearValue(opts = {}) { |
|
||||
this.set({ value: undefined, status: '' }, opts); |
|
||||
return this; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Update value |
|
||||
* @param {any} value |
|
||||
* @param {Boolen} [complete=true] Indicates if it's a final state |
|
||||
* @param {Object} [opts={}] Options |
|
||||
*/ |
|
||||
setValue(value, complete = 1, opts = {}) { |
|
||||
const parsed = this.parseValue(value); |
|
||||
const avoidStore = !complete; |
|
||||
!avoidStore && |
|
||||
this.set({ value: undefined }, { avoidStore, silent: true }); |
|
||||
this.set(parsed, { avoidStore, ...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 |
|
||||
* @param {Boolen} [complete=true] Indicates if it's a final state |
|
||||
* @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) { |
init() {} |
||||
return result; |
|
||||
} |
|
||||
|
|
||||
const args = []; |
/** |
||||
let valueStr = `${result.value}`; |
* Clear the value |
||||
let start = valueStr.indexOf('(') + 1; |
* @return {this} |
||||
let end = valueStr.lastIndexOf(')'); |
* @private |
||||
const functionName = valueStr.substring(0, start - 1); |
*/ |
||||
if (functionName) result.functionName = functionName; |
clearValue(opts = {}) { |
||||
args.push(start); |
this.set({ value: undefined, status: '' }, opts); |
||||
|
return this; |
||||
// 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); |
/** |
||||
|
* Update value |
||||
|
* @param {any} value |
||||
|
* @param {Boolen} [complete=true] Indicates if it's a final state |
||||
|
* @param {Object} [opts={}] Options |
||||
|
* @private |
||||
|
*/ |
||||
|
setValue(value, complete = 1, opts = {}) { |
||||
|
const parsed = this.parseValue(value); |
||||
|
const avoidStore = !complete; |
||||
|
!avoidStore && this.set({ value: undefined }, { avoidStore, silent: true }); |
||||
|
this.set(parsed, { avoidStore, ...opts }); |
||||
|
} |
||||
|
|
||||
if (opts.numeric) { |
/** |
||||
const num = parseFloat(result.value); |
* Like `setValue` but, in addition, prevents the update of the input element |
||||
result.unit = result.value.replace(num, ''); |
* as the changes should come from the input itself. |
||||
result.value = num; |
* This method is useful with the definition of custom properties |
||||
} |
* @param {any} value |
||||
|
* @param {Boolen} [complete=true] Indicates if it's a final state |
||||
|
* @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} |
||||
|
* @private |
||||
|
* @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; |
return result; |
||||
}, |
} |
||||
|
|
||||
/** |
|
||||
* Helper function to safely split a string of values. |
|
||||
* Useful when style values are inside functions |
|
||||
* eg: |
|
||||
* -> input: 'value(1,2,4), 123, value(4,5)' -- default separator: ',' |
|
||||
* -> output: ['value(1,2,4)', '123', 'value(4,5)'] |
|
||||
* @param {String} values Values to split |
|
||||
* @param {String} [separator] Separator |
|
||||
*/ |
|
||||
splitValues(values, separator = ',') { |
|
||||
const res = []; |
|
||||
const op = '('; |
|
||||
const cl = ')'; |
|
||||
let curr = ''; |
|
||||
let acc = 0; |
|
||||
|
|
||||
(values || '').split('').forEach(str => { |
|
||||
if (str == op) { |
|
||||
acc++; |
|
||||
curr = curr + op; |
|
||||
} else if (str == cl && acc > 0) { |
|
||||
acc--; |
|
||||
curr = curr + cl; |
|
||||
} else if (str === separator && acc == 0) { |
|
||||
res.push(curr); |
|
||||
curr = ''; |
|
||||
} else { |
|
||||
curr = curr + str; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
curr !== '' && res.push(curr); |
|
||||
|
|
||||
return res.map(i => i.trim()); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 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'); |
|
||||
const def = this.getDefaultValue(); |
|
||||
let value = isUndefined(val) ? this.get('value') : val; |
|
||||
const hasValue = !isUndefined(value) && value !== ''; |
|
||||
|
|
||||
if (value && def && value === def) { |
|
||||
return def; |
|
||||
} |
|
||||
|
|
||||
if (fn && hasValue) { |
const args = []; |
||||
const fnParameter = |
let valueStr = `${result.value}`; |
||||
fn === 'url' ? `'${value.replace(/'/g, '')}'` : value; |
let start = valueStr.indexOf('(') + 1; |
||||
value = `${fn}(${fnParameter})`; |
let end = valueStr.lastIndexOf(')'); |
||||
} |
const functionName = valueStr.substring(0, start - 1); |
||||
|
if (functionName) result.functionName = functionName; |
||||
|
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 (hasValue && this.get('important')) { |
if (opts.numeric) { |
||||
value = `${value} !important`; |
const num = parseFloat(result.value); |
||||
|
result.unit = result.value.replace(num, ''); |
||||
|
result.value = num; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Helper function to safely split a string of values. |
||||
|
* Useful when style values are inside functions |
||||
|
* eg: |
||||
|
* -> input: 'value(1,2,4), 123, value(4,5)' -- default separator: ',' |
||||
|
* -> output: ['value(1,2,4)', '123', 'value(4,5)'] |
||||
|
* @param {String} values Values to split |
||||
|
* @param {String} [separator] Separator |
||||
|
* @private |
||||
|
*/ |
||||
|
splitValues(values, separator = ',') { |
||||
|
const res = []; |
||||
|
const op = '('; |
||||
|
const cl = ')'; |
||||
|
let curr = ''; |
||||
|
let acc = 0; |
||||
|
|
||||
|
(values || '').split('').forEach(str => { |
||||
|
if (str == op) { |
||||
|
acc++; |
||||
|
curr = curr + op; |
||||
|
} else if (str == cl && acc > 0) { |
||||
|
acc--; |
||||
|
curr = curr + cl; |
||||
|
} else if (str === separator && acc == 0) { |
||||
|
res.push(curr); |
||||
|
curr = ''; |
||||
|
} else { |
||||
|
curr = curr + str; |
||||
} |
} |
||||
|
}); |
||||
|
|
||||
return value || ''; |
curr !== '' && res.push(curr); |
||||
|
|
||||
|
return res.map(i => i.trim()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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'); |
||||
|
const def = this.getDefaultValue(); |
||||
|
let value = isUndefined(val) ? this.get('value') : val; |
||||
|
const hasValue = !isUndefined(value) && value !== ''; |
||||
|
|
||||
|
if (value && def && value === def) { |
||||
|
return def; |
||||
} |
} |
||||
}, |
|
||||
{ |
if (fn && hasValue) { |
||||
callParentInit(property, ctx, props, opts = {}) { |
const fnParameter = fn === 'url' ? `'${value.replace(/'/g, '')}'` : value; |
||||
property.prototype.initialize.apply(ctx, [ |
value = `${fn}(${fnParameter})`; |
||||
props, |
|
||||
{ |
|
||||
...opts, |
|
||||
skipInit: 1 |
|
||||
} |
|
||||
]); |
|
||||
}, |
|
||||
|
|
||||
callInit(context, props, opts = {}) { |
|
||||
!opts.skipInit && context.init(props, opts); |
|
||||
} |
} |
||||
} |
|
||||
); |
|
||||
|
|
||||
export default Property; |
if (hasValue && this.get('important')) { |
||||
|
value = `${value} !important`; |
||||
|
} |
||||
|
|
||||
|
return value || ''; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Property.callParentInit = function(property, ctx, props, opts = {}) { |
||||
|
property.prototype.initialize.apply(ctx, [ |
||||
|
props, |
||||
|
{ |
||||
|
...opts, |
||||
|
skipInit: 1 |
||||
|
} |
||||
|
]); |
||||
|
}; |
||||
|
|
||||
|
Property.callInit = function(context, props, opts = {}) { |
||||
|
!opts.skipInit && context.init(props, opts); |
||||
|
}; |
||||
|
|
||||
|
Property.prototype.defaults = { |
||||
|
name: '', |
||||
|
property: '', |
||||
|
type: '', |
||||
|
defaults: '', |
||||
|
info: '', |
||||
|
value: '', |
||||
|
icon: '', |
||||
|
functionName: '', |
||||
|
status: '', |
||||
|
visible: true, |
||||
|
fixedValues: ['initial', 'inherit'], |
||||
|
|
||||
|
// If true, the property will be forced to be full width
|
||||
|
full: 0, |
||||
|
|
||||
|
// If true to the value will be added '!important'
|
||||
|
important: 0, |
||||
|
|
||||
|
// If true, will be hidden by default and will show up only for targets
|
||||
|
// which require this property (via `stylable-require`)
|
||||
|
// Use case:
|
||||
|
// you can add all SVG CSS properties with toRequire as true
|
||||
|
// and then require them on SVG Components
|
||||
|
toRequire: 0, |
||||
|
|
||||
|
// Specifies dependency on other properties of the selected object.
|
||||
|
// Property is shown only when all conditions are matched.
|
||||
|
//
|
||||
|
// example: { display: ['flex', 'block'], position: ['absolute'] };
|
||||
|
// in this case the property is only shown when display is
|
||||
|
// of value 'flex' or 'block' AND position is 'absolute'
|
||||
|
requires: null, |
||||
|
|
||||
|
// Specifies dependency on properties of the parent of the selected object.
|
||||
|
// Property is shown only when all conditions are matched.
|
||||
|
requiresParent: null |
||||
|
}; |
||||
|
|||||
Loading…
Reference in new issue