|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,77 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## getId |
|||
|
|||
Get layer id. |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
## getIndex |
|||
|
|||
Get layer index. |
|||
|
|||
Returns **[Number][2]** |
|||
|
|||
## getValues |
|||
|
|||
Get layer values. |
|||
|
|||
### Parameters |
|||
|
|||
* `opts` **[Object][3]** Options (optional, default `{}`) |
|||
|
|||
* `opts.camelCase` **[Boolean][4]?** Return property names in camelCase. |
|||
|
|||
Returns **[Object][3]** |
|||
|
|||
## getLabel |
|||
|
|||
Get layer label. |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
## isSelected |
|||
|
|||
Check if the layer is selected. |
|||
|
|||
Returns **[Boolean][4]** |
|||
|
|||
## select |
|||
|
|||
Select the layer. |
|||
|
|||
## remove |
|||
|
|||
Remove the layer. |
|||
|
|||
## move |
|||
|
|||
Move layer to a new index. |
|||
|
|||
### Parameters |
|||
|
|||
* `index` **[Number][2]** New index |
|||
|
|||
## getStylePreview |
|||
|
|||
Get style object for the preview. |
|||
|
|||
### Parameters |
|||
|
|||
* `opts` **[Object][3]** Options. Same of `PropertyStack.getStyleFromLayer` (optional, default `{}`) |
|||
|
|||
Returns **[Object][3]** Style object |
|||
|
|||
## hasPreview |
|||
|
|||
Check if the property has the preview enabled for this layer. |
|||
|
|||
Returns **[Boolean][4]** |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
|
|||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
@ -0,0 +1,164 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## Property |
|||
|
|||
|
|||
|
|||
### Properties |
|||
|
|||
* `id` **[String][1]** Property id, eg. `my-property-id`. |
|||
* `property` **[String][1]** Related CSS property name, eg. `text-align`. |
|||
* `default` **[String][1]** Defaul value of the property. |
|||
* `label` **[String][1]** Label to use in UI, eg. `Text Align`. |
|||
* `onChange` **[Function][2]?** Change callback. |
|||
```js |
|||
onChange: ({ property, from, to }) => { |
|||
console.log(`Changed property`, property.getName(), { from, to }); |
|||
} |
|||
``` |
|||
|
|||
### getId |
|||
|
|||
Get property id. |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### getType |
|||
|
|||
Get the property type. |
|||
The type of the property is defined on property creation and based on its value the proper Property class is assigned. |
|||
The default type is `base`. |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### getName |
|||
|
|||
Get name (the CSS property name). |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### getLabel |
|||
|
|||
Get property label. |
|||
|
|||
#### Parameters |
|||
|
|||
* `opts` **[Object][3]** Options (optional, default `{}`) |
|||
|
|||
* `opts.locale` **[Boolean][4]** Use the locale string from i18n module (optional, default `true`) |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### getValue |
|||
|
|||
Get property value. |
|||
|
|||
#### Parameters |
|||
|
|||
* `opts` **[Object][3]** Options (optional, default `{}`) |
|||
|
|||
* `opts.noDefault` **[Boolean][4]** Avoid returning the default value (optional, default `false`) |
|||
|
|||
Returns **[String][1]** |
|||
|
|||
### hasValue |
|||
|
|||
Check if the property has value. |
|||
|
|||
#### Parameters |
|||
|
|||
* `opts` **[Object][3]** Options (optional, default `{}`) |
|||
|
|||
* `opts.noParent` **[Boolean][4]** Ignore the value if it comes from the parent target. (optional, default `false`) |
|||
|
|||
Returns **[Boolean][4]** |
|||
|
|||
### hasValueParent |
|||
|
|||
Indicates if the current value is coming from a parent target (eg. another CSSRule). |
|||
|
|||
Returns **[Boolean][4]** |
|||
|
|||
### getStyle |
|||
|
|||
Get the CSS style object of the property. |
|||
|
|||
#### Parameters |
|||
|
|||
* `opts` **[Object][3]** Options (optional, default `{}`) |
|||
|
|||
* `opts.camelCase` **[Boolean][4]?** Return property name in camelCase. |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
// In case the property is `color` with a value of `red`. |
|||
console.log(property.getStyle()); |
|||
// { color: 'red' }; |
|||
``` |
|||
|
|||
Returns **[Object][3]** |
|||
|
|||
### getDefaultValue |
|||
|
|||
Get the default value. |
|||
|
|||
Returns **[string][1]** |
|||
|
|||
### upValue |
|||
|
|||
Update the value. |
|||
The change is also propagated to the selected targets (eg. CSS rule). |
|||
|
|||
#### Parameters |
|||
|
|||
* `value` **[String][1]** New value |
|||
* `opts` **[Object][3]** Options (optional, default `{}`) |
|||
|
|||
* `opts.partial` **[Boolean][4]** If `true` the update on targets won't be considered complete (not stored in UndoManager) (optional, default `false`) |
|||
* `opts.noTarget` **[Boolean][4]** If `true` the change won't be propagated to selected targets. (optional, default `false`) |
|||
|
|||
### isVisible |
|||
|
|||
Check if the property is visible |
|||
|
|||
Returns **[Boolean][4]** |
|||
|
|||
### clear |
|||
|
|||
Clear the value. |
|||
The change is also propagated to the selected targets (eg. the css property is cleared). |
|||
|
|||
#### Parameters |
|||
|
|||
* `opts` **[Object][3]** Options (optional, default `{}`) |
|||
|
|||
* `opts.noTarget` **[Boolean][4]** If `true` the change won't be propagated to selected targets. (optional, default `false`) |
|||
|
|||
### canClear |
|||
|
|||
Indicates if the current value comes directly from the selected target and so can be cleared. |
|||
|
|||
Returns **[Boolean][4]** |
|||
|
|||
### getParent |
|||
|
|||
If the current property is a sub-property, this will return the parent Property. |
|||
|
|||
Returns **([[Property][5]] | null)** |
|||
|
|||
### isFull |
|||
|
|||
Indicates if the property is full-width in UI. |
|||
|
|||
Returns **[Boolean][4]** |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
|
|||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
|
|||
[5]: #property |
|||
@ -0,0 +1,113 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## PropertyComposite |
|||
|
|||
**Extends Property** |
|||
|
|||
[Property]: property.html |
|||
|
|||
### Properties |
|||
|
|||
* `properties` **[Array][1]<[Object][2]>** Array of sub properties, eg. `[{ type: 'number', property: 'margin-top' }, ...]` |
|||
* `detached` **[Boolean][3]?** Indicate if the final CSS property is splitted (detached: `margin-top: X; margin-right: Y; ...`) or combined (not detached: `margin: X Y ...;`) |
|||
* `separator` **([String][4] | [RegExp][5])?** Value used to split property values, default `" "`. |
|||
* `join` **[String][4]?** Value used to join property values, default `" "`. |
|||
* `fromStyle` **[Function][6]?** Custom logic for getting property values from the target style object. |
|||
```js |
|||
fromStyle: (style) => { |
|||
const margins = parseMarginShorthand(style.margin); |
|||
return { |
|||
'margin-top': margins.top, |
|||
// ... |
|||
}; |
|||
} |
|||
``` |
|||
* `toStyle` **[Function][6]?** Custom logic for creating the CSS style object to apply on selected targets. |
|||
```js |
|||
toStyle: (values) => { |
|||
const top = values['margin-top'] || 0; |
|||
const right = values['margin-right'] || 0; |
|||
// ... |
|||
return { |
|||
margin: `${top} ${right} ...`, |
|||
}; |
|||
} |
|||
``` |
|||
|
|||
### getProperties |
|||
|
|||
Get properties. |
|||
|
|||
Returns **[Array][1]<[Property]>** |
|||
|
|||
### getProperty |
|||
|
|||
Get property by id. |
|||
|
|||
#### Parameters |
|||
|
|||
* `id` **[String][4]** Property id. |
|||
|
|||
Returns **([Property] | null)** |
|||
|
|||
### getPropertyAt |
|||
|
|||
Get property at index. |
|||
|
|||
#### Parameters |
|||
|
|||
* `index` **[Number][7]** |
|||
|
|||
Returns **([Property] | null)** |
|||
|
|||
### isDetached |
|||
|
|||
Check if the property is detached. |
|||
|
|||
Returns **[Boolean][3]** |
|||
|
|||
### getValues |
|||
|
|||
Get current values of properties. |
|||
|
|||
#### Parameters |
|||
|
|||
* `opts` **[Object][2]** Options (optional, default `{}`) |
|||
|
|||
* `opts.byName` **[Boolean][3]** Use property names as a key instead of the id. (optional, default `false`) |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
// In case the property is `margin` with sub properties like `margin-top`, `margin-right`, etc. |
|||
console.log(property.getValues()); |
|||
// { 'margin-top': '10px', 'margin-right': '20px', ... }; |
|||
``` |
|||
|
|||
Returns **[Object][2]** |
|||
|
|||
### getSeparator |
|||
|
|||
Get property separator. |
|||
|
|||
Returns **[RegExp][5]** |
|||
|
|||
### getJoin |
|||
|
|||
Get the join value. |
|||
|
|||
Returns **[String][4]** |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
|
|||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp |
|||
|
|||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function |
|||
|
|||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number |
|||
@ -0,0 +1,66 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## PropertyNumber |
|||
|
|||
**Extends Property** |
|||
|
|||
### Properties |
|||
|
|||
* `units` **[Array][1]<[String][2]>** Array of units, eg. `['px', '%']` |
|||
* `min` **[Number][3]** Minimum value. |
|||
* `max` **[Number][3]** Maximum value. |
|||
* `step` **[Number][3]** Step value. |
|||
|
|||
### getUnits |
|||
|
|||
Get property units. |
|||
|
|||
Returns **[Array][1]<[String][2]>** |
|||
|
|||
### getUnit |
|||
|
|||
Get property unit value. |
|||
|
|||
Returns **[String][2]** |
|||
|
|||
### getMin |
|||
|
|||
Get min value. |
|||
|
|||
Returns **[Number][3]** |
|||
|
|||
### getMax |
|||
|
|||
Get max value. |
|||
|
|||
Returns **[Number][3]** |
|||
|
|||
### getStep |
|||
|
|||
Get step value. |
|||
|
|||
Returns **[Number][3]** |
|||
|
|||
### upUnit |
|||
|
|||
Update property unit value. |
|||
The change is also propagated to the selected targets. |
|||
|
|||
#### Parameters |
|||
|
|||
* `unit` **[String][2]** New unit value |
|||
* `opts` **[Object][4]** Options (optional, default `{}`) |
|||
|
|||
* `opts.noTarget` **[Boolean][5]** If `true` the change won't be propagated to selected targets. (optional, default `false`) |
|||
|
|||
Returns **[String][2]** |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number |
|||
|
|||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
|
|||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
@ -0,0 +1,78 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## PropertySelect |
|||
|
|||
**Extends Property** |
|||
|
|||
### Properties |
|||
|
|||
* `options` **[Array][1]<[Object][2]>** Array of option definitions. |
|||
```js |
|||
options: [ |
|||
{ id: '100', label: 'Set 100' }, |
|||
{ id: '200', label: 'Set 200' }, |
|||
] |
|||
``` |
|||
|
|||
### getOptions |
|||
|
|||
Get available options. |
|||
|
|||
Returns **[Array][1]<[Object][2]>** Array of options |
|||
|
|||
### getOption |
|||
|
|||
Get current selected option or by id. |
|||
|
|||
#### Parameters |
|||
|
|||
* `id` **[String][3]?** Option id. |
|||
|
|||
Returns **([Object][2] | null)** |
|||
|
|||
### setOptions |
|||
|
|||
Update options. |
|||
|
|||
#### Parameters |
|||
|
|||
* `value` **[Array][1]<[Object][2]>** New array of options, eg. `[{ id: 'val-1', label: 'Value 1' }]` (optional, default `[]`) |
|||
|
|||
### addOption |
|||
|
|||
Add new option. |
|||
|
|||
#### Parameters |
|||
|
|||
* `value` **[Object][2]** Option object, eg. `{ id: 'val-1', label: 'Value 1' }` |
|||
|
|||
### getOptionId |
|||
|
|||
Get the option id from the option object. |
|||
|
|||
#### Parameters |
|||
|
|||
* `option` **[Object][2]** Option object |
|||
|
|||
Returns **[String][3]** Option id |
|||
|
|||
### getOptionLabel |
|||
|
|||
Get option label. |
|||
|
|||
#### Parameters |
|||
|
|||
* `id` **([String][3] | [Object][2])** Option id or the option object |
|||
* `opts` **[Object][2]** Options (optional, default `{}`) |
|||
|
|||
* `opts.locale` **[Boolean][4]** Use the locale string from i18n module (optional, default `true`) |
|||
|
|||
Returns **[String][3]** Option label |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
@ -0,0 +1,214 @@ |
|||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> |
|||
|
|||
## PropertyStack |
|||
|
|||
**Extends PropertyComposite** |
|||
|
|||
[Layer]: layer.html |
|||
|
|||
### Properties |
|||
|
|||
* `preview` **[Boolean][1]?** Indicate if the layer should display a preview. |
|||
* `layerSeparator` **([String][2] | [RegExp][3])?** The separator used to split layer values. |
|||
* `layerJoin` **[String][2]?** Value used to join layer values. |
|||
* `layerLabel` **[Function][4]?** Custom logic for creating layer labels. |
|||
```js |
|||
layerLabel: (layer) => { |
|||
const values = layer.getValues(); |
|||
return `A: ${values['prop-a']} B: ${values['prop-b']}`; |
|||
} |
|||
``` |
|||
|
|||
### getLayers |
|||
|
|||
Get all available layers. |
|||
|
|||
Returns **[Array][5]<[Layer]>** |
|||
|
|||
### getLayer |
|||
|
|||
Get layer by index. |
|||
|
|||
#### Parameters |
|||
|
|||
* `index` **[Number][6]** Layer index position. (optional, default `0`) |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
// Get the first layer |
|||
const layerFirst = property.getLayer(0); |
|||
// Get the last layer |
|||
const layers = this.getLayers(); |
|||
const layerLast = property.getLayer(layers.length - 1); |
|||
``` |
|||
|
|||
Returns **([Layer] | null)** |
|||
|
|||
### getSelectedLayer |
|||
|
|||
Get selected layer. |
|||
|
|||
Returns **([Layer] | null)** |
|||
|
|||
### selectLayer |
|||
|
|||
Select layer. |
|||
Without a selected layer any update made on inner properties has no effect. |
|||
|
|||
#### Parameters |
|||
|
|||
* `layer` **[Layer]** Layer to select |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
const layer = property.getLayer(0); |
|||
property.selectLayer(layer); |
|||
``` |
|||
|
|||
### selectLayerAt |
|||
|
|||
Select layer by index. |
|||
|
|||
#### Parameters |
|||
|
|||
* `index` **[Number][6]** Index of the layer to select. (optional, default `0`) |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
property.selectLayerAt(1); |
|||
``` |
|||
|
|||
### moveLayer |
|||
|
|||
Move layer by index. |
|||
|
|||
#### Parameters |
|||
|
|||
* `layer` **[Layer]** Layer to move. |
|||
* `index` **[Number][6]** New layer index. (optional, default `0`) |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
const layer = property.getLayer(1); |
|||
property.moveLayer(layer, 0); |
|||
``` |
|||
|
|||
### addLayer |
|||
|
|||
Add new layer to the stack. |
|||
|
|||
#### Parameters |
|||
|
|||
* `props` **[Object][7]** Custom property values to use in a new layer. (optional, default `{}`) |
|||
* `opts` **[Object][7]** Options (optional, default `{}`) |
|||
|
|||
* `opts.at` **[Number][6]?** Position index (by default the layer will be appended at the end). |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
// Add new layer at the beginning of the stack with custom values |
|||
property.addLayer({ 'sub-prop1': 'value1', 'sub-prop2': 'value2' }, { at: 0 }); |
|||
``` |
|||
|
|||
Returns **[Layer]** Added layer. |
|||
|
|||
### removeLayer |
|||
|
|||
Remove layer. |
|||
|
|||
#### Parameters |
|||
|
|||
* `layer` **[Layer]** Layer to remove. |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
const layer = property.getLayer(0); |
|||
property.removeLayer(layer); |
|||
``` |
|||
|
|||
Returns **[Layer]** Removed layer |
|||
|
|||
### removeLayerAt |
|||
|
|||
Remove layer by index. |
|||
|
|||
#### Parameters |
|||
|
|||
* `index` **[Number][6]** Index of the layer to remove (optional, default `0`) |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
property.removeLayerAt(0); |
|||
``` |
|||
|
|||
Returns **([Layer] | null)** Removed layer |
|||
|
|||
### getLayerLabel |
|||
|
|||
Get the layer label. The label can be customized with the `layerLabel` property. |
|||
|
|||
#### Parameters |
|||
|
|||
* `layer` **[Layer]** |
|||
|
|||
#### Examples |
|||
|
|||
```javascript |
|||
const layer = this.getLayer(1); |
|||
const label = this.getLayerLabel(layer); |
|||
``` |
|||
|
|||
Returns **[String][2]** |
|||
|
|||
### getStyleFromLayer |
|||
|
|||
Get style object from the layer. |
|||
|
|||
#### Parameters |
|||
|
|||
* `layer` **[Layer]** |
|||
* `opts` **[Object][7]** Options (optional, default `{}`) |
|||
|
|||
* `opts.camelCase` **[Boolean][1]?** Return property names in camelCase. |
|||
* `opts.number` **[Object][7]?** Limit the result of the number types, eg. `number: { min: -3, max: 3 }` |
|||
|
|||
Returns **[Object][7]** Style object |
|||
|
|||
### getStylePreview |
|||
|
|||
Get preview style object from the layer. |
|||
If the property has `preview: false` the returned object will be empty. |
|||
|
|||
#### Parameters |
|||
|
|||
* `layer` **[Layer]** |
|||
* `opts` **[Object][7]** Options. Same of `getStyleFromLayer` (optional, default `{}`) |
|||
|
|||
Returns **[Object][7]** Style object |
|||
|
|||
### getLayerSeparator |
|||
|
|||
Get layer separator. |
|||
|
|||
Returns **[RegExp][3]** |
|||
|
|||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean |
|||
|
|||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String |
|||
|
|||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp |
|||
|
|||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function |
|||
|
|||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array |
|||
|
|||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number |
|||
|
|||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object |
|||
@ -0,0 +1 @@ |
|||
export { View as default } from 'backbone'; |
|||
@ -1,2 +1,3 @@ |
|||
export { default as Model } from './Model'; |
|||
export { default as Collection } from './Collection'; |
|||
export { default as View } from './View'; |
|||
|
|||
@ -1,29 +1,91 @@ |
|||
export default { |
|||
stylePrefix: 'sm-', |
|||
|
|||
sectors: [], |
|||
// Default sectors and properties
|
|||
sectors: [ |
|||
{ |
|||
name: 'General', |
|||
open: false, |
|||
properties: ['display', 'float', 'position', 'top', 'right', 'left', 'bottom'], |
|||
}, |
|||
{ |
|||
name: 'Flex', |
|||
open: false, |
|||
properties: [ |
|||
'flex-direction', |
|||
'flex-wrap', |
|||
'justify-content', |
|||
'align-items', |
|||
'align-content', |
|||
'order', |
|||
'flex-basis', |
|||
'flex-grow', |
|||
'flex-shrink', |
|||
'align-self', |
|||
], |
|||
}, |
|||
{ |
|||
name: 'Dimension', |
|||
open: false, |
|||
properties: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'], |
|||
}, |
|||
{ |
|||
name: 'Typography', |
|||
open: false, |
|||
properties: [ |
|||
'font-family', |
|||
'font-size', |
|||
'font-weight', |
|||
'letter-spacing', |
|||
'color', |
|||
'line-height', |
|||
'text-align', |
|||
'text-shadow', |
|||
], |
|||
}, |
|||
{ |
|||
name: 'Decorations', |
|||
open: false, |
|||
properties: ['background-color', 'border-radius', 'border', 'box-shadow', 'background'], |
|||
}, |
|||
{ |
|||
name: 'Extra', |
|||
open: false, |
|||
properties: ['opacity', 'transition'], |
|||
}, |
|||
], |
|||
|
|||
// Specify the element to use as a container, string (query) or HTMLElement
|
|||
// With the empty value, nothing will be rendered
|
|||
appendTo: '', |
|||
|
|||
// Style prefix
|
|||
stylePrefix: 'sm-', |
|||
|
|||
// Avoid rendering the default style manager.
|
|||
custom: false, |
|||
|
|||
// Hide the property in case it's not stylable for the
|
|||
// selected component (each component has 'stylable' property)
|
|||
// @deprecated
|
|||
hideNotStylable: true, |
|||
|
|||
// Highlight changed properties of the selected component
|
|||
// @deprecated
|
|||
highlightChanged: true, |
|||
|
|||
// Highlight computed properties of the selected component
|
|||
// @deprecated
|
|||
highlightComputed: true, |
|||
|
|||
// Show computed properties of the selected component, if this value
|
|||
// is set to false, highlightComputed will not take effect
|
|||
// @deprecated
|
|||
showComputed: true, |
|||
|
|||
// Adds the possibility to clear property value from the target style
|
|||
// @deprecated
|
|||
clearProperties: true, |
|||
|
|||
// Properties not to take in account for computed styles
|
|||
avoidComputed: ['width', 'height'] |
|||
// @deprecated
|
|||
avoidComputed: ['width', 'height'], |
|||
}; |
|||
|
|||
@ -1,68 +1,111 @@ |
|||
import Backbone from 'backbone'; |
|||
import Properties from './Properties'; |
|||
import { Model } from 'common'; |
|||
import { camelCase } from 'utils/mixins'; |
|||
|
|||
export default Backbone.Model.extend({ |
|||
defaults: { |
|||
index: '', |
|||
value: '', |
|||
values: {}, |
|||
active: false, |
|||
preview: false, |
|||
properties: [] |
|||
}, |
|||
export default class Layer extends Model { |
|||
defaults() { |
|||
return { |
|||
values: {}, |
|||
}; |
|||
} |
|||
|
|||
initialize() { |
|||
const prp = this.get('properties'); |
|||
var value = this.get('value'); |
|||
this.set( |
|||
'properties', |
|||
prp instanceof Properties ? prp : new Properties(prp) |
|||
); |
|||
const props = this.get('properties'); |
|||
props.forEach(this.onPropAdd, this); |
|||
this.listenTo(props, 'add', this.onPropAdd); |
|||
const cl = this.collection; |
|||
this.prop = cl?.prop; |
|||
} |
|||
|
|||
// If there is no value I'll try to get it from values
|
|||
// I need value setted to make preview working
|
|||
if (!value) { |
|||
var val = ''; |
|||
var values = this.get('values'); |
|||
/** |
|||
* Get layer id. |
|||
* @returns {String} |
|||
*/ |
|||
getId() { |
|||
return this.cid; |
|||
} |
|||
|
|||
for (var prop in values) { |
|||
val += ' ' + values[prop]; |
|||
} |
|||
/** |
|||
* Get layer index. |
|||
* @returns {Number} |
|||
*/ |
|||
getIndex() { |
|||
const coll = this.collection; |
|||
return coll ? coll.indexOf(this) : -1; |
|||
} |
|||
|
|||
this.set('value', val.trim()); |
|||
} |
|||
}, |
|||
/** |
|||
* Get layer values. |
|||
* @param {Object} [opts={}] Options |
|||
* @param {Boolean} [opts.camelCase] Return property names in camelCase. |
|||
* @returns {Object} |
|||
*/ |
|||
getValues(opts = {}) { |
|||
const values = this.get('values'); |
|||
|
|||
onPropAdd(prop) { |
|||
const coll = this.collection; |
|||
prop.parent = coll && coll.property; |
|||
}, |
|||
return opts.camelCase |
|||
? Object.keys(values).reduce((res, key) => { |
|||
res[camelCase(key)] = values[key]; |
|||
return res; |
|||
}, {}) |
|||
: values; |
|||
} |
|||
|
|||
/** |
|||
* Get property at some index |
|||
* @param {Number} index |
|||
* @return {Object} |
|||
* Get layer label. |
|||
* @returns {String} |
|||
*/ |
|||
getPropertyAt(index) { |
|||
return this.get('properties').at(index); |
|||
}, |
|||
getLabel() { |
|||
return this.prop?.getLayerLabel(this); |
|||
} |
|||
|
|||
getPropertyValue(property) { |
|||
let result = ''; |
|||
this.get('properties').each(prop => { |
|||
if (prop.get('property') == property) { |
|||
result = prop.getFullValue(); |
|||
} |
|||
}); |
|||
return result; |
|||
}, |
|||
/** |
|||
* Check if the layer is selected. |
|||
* @returns {Boolean} |
|||
*/ |
|||
isSelected() { |
|||
return this.prop?.getSelectedLayer() === this; |
|||
} |
|||
|
|||
getFullValue() { |
|||
let result = []; |
|||
this.get('properties').each(prop => result.push(prop.getFullValue())); |
|||
return result.join(' ').trim(); |
|||
/** |
|||
* Select the layer. |
|||
*/ |
|||
select() { |
|||
return this.prop?.selectLayer(this); |
|||
} |
|||
|
|||
/** |
|||
* Remove the layer. |
|||
*/ |
|||
remove() { |
|||
return this.prop?.removeLayer(this); |
|||
} |
|||
|
|||
/** |
|||
* Move layer to a new index. |
|||
* @param {Number} index New index |
|||
*/ |
|||
move(index) { |
|||
return this.prop?.moveLayer(this, index); |
|||
} |
|||
|
|||
/** |
|||
* Get style object for the preview. |
|||
* @param {Object} [opts={}] Options. Same of `PropertyStack.getStyleFromLayer` |
|||
* @returns {Object} Style object |
|||
*/ |
|||
getStylePreview(opts = {}) { |
|||
return this.prop?.getStylePreview(this, opts); |
|||
} |
|||
|
|||
/** |
|||
* Check if the property has the preview enabled for this layer. |
|||
* @returns {Boolean} |
|||
*/ |
|||
hasPreview() { |
|||
return !!this.prop?.get('preview'); |
|||
} |
|||
|
|||
upValues(props = {}) { |
|||
return this.set('values', { |
|||
...this.getValues(), |
|||
...props, |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@ -1,123 +1,10 @@ |
|||
import { isUndefined } from 'underscore'; |
|||
import Backbone from 'backbone'; |
|||
import { Collection } from 'common'; |
|||
import Layer from './Layer'; |
|||
|
|||
export default Backbone.Collection.extend({ |
|||
model: Layer, |
|||
|
|||
initialize() { |
|||
this.idx = 1; |
|||
this.on('add', this.onAdd); |
|||
this.on('reset', this.onReset); |
|||
}, |
|||
|
|||
onAdd(model, c, opts) { |
|||
if (!opts.noIncrement) model.set('index', this.idx++); |
|||
opts.active && this.active(this.indexOf(model)); |
|||
}, |
|||
|
|||
onReset() { |
|||
this.idx = 1; |
|||
}, |
|||
|
|||
getSeparator() { |
|||
const { property } = this; |
|||
return property ? property.get('layerSeparator') : ', '; |
|||
}, |
|||
|
|||
/** |
|||
* Get layers from a value string (for not detached properties), |
|||
* example of input: |
|||
* `layer1Value, layer2Value, layer3Value, ...` |
|||
* @param {string} value |
|||
* @return {Array} |
|||
* @private |
|||
*/ |
|||
getLayersFromValue(value) { |
|||
const layers = []; |
|||
// Remove spaces inside functions, eg:
|
|||
// From: 1px 1px rgba(2px, 2px, 2px), 2px 2px rgba(3px, 3px, 3px)
|
|||
// To: 1px 1px rgba(2px,2px,2px), 2px 2px rgba(3px,3px,3px)
|
|||
value.replace(/\(([\w\s,.]*)\)/g, match => { |
|||
var cleaned = match.replace(/,\s*/g, ','); |
|||
value = value.replace(match, cleaned); |
|||
}); |
|||
const layerValues = value ? value.split(this.getSeparator()) : []; |
|||
layerValues.forEach(layerValue => { |
|||
layers.push({ properties: this.properties.parseValue(layerValue) }); |
|||
}); |
|||
return layers; |
|||
}, |
|||
|
|||
/** |
|||
* Get layers from a style object (for detached properties), |
|||
* example of input: |
|||
* { |
|||
* subPropname1: sub-propvalue11, sub-propvalue12, sub-propvalue13, ... |
|||
* subPropname2: sub-propvalue21, sub-propvalue22, sub-propvalue23, ... |
|||
* subPropname3: sub-propvalue31, sub-propvalue32, sub-propvalue33, ... |
|||
* } |
|||
* @param {Object} styleObj |
|||
* @return {Array} |
|||
* @private |
|||
*/ |
|||
getLayersFromStyle(styleObj) { |
|||
const layers = []; |
|||
const properties = this.properties; |
|||
|
|||
properties.each(propModel => { |
|||
const style = styleObj[propModel.get('property')]; |
|||
const values = style ? style.split(', ') : []; |
|||
values.forEach((value, i) => { |
|||
value = propModel.parseValue(value.trim()).value; |
|||
const layer = layers[i]; |
|||
const propertyObj = { ...propModel.attributes, ...{ value } }; |
|||
|
|||
if (layer) { |
|||
layer.properties.push(propertyObj); |
|||
} else { |
|||
layers[i] = { |
|||
properties: [propertyObj] |
|||
}; |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
// Now whit all layers in, will check missing properties
|
|||
layers.forEach(layer => { |
|||
const layerProprs = layer.properties.map(prop => prop.property); |
|||
properties.each(propModel => { |
|||
const propertyName = propModel.get('property'); |
|||
|
|||
if (layerProprs.indexOf(propertyName) < 0) { |
|||
layer.properties.push({ ...propModel.attributes }); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
return layers; |
|||
}, |
|||
|
|||
active(index) { |
|||
this.each(layer => layer.set('active', 0)); |
|||
const layer = this.at(index); |
|||
layer && layer.set('active', 1); |
|||
}, |
|||
|
|||
getFullValue() { |
|||
let result = []; |
|||
this.each(layer => result.push(layer.getFullValue())); |
|||
return result.join(this.getSeparator()); |
|||
}, |
|||
|
|||
getPropertyValues(property, defValue) { |
|||
const result = []; |
|||
this.each(layer => { |
|||
const value = layer.getPropertyValue(property); |
|||
value |
|||
? result.push(value) |
|||
: !isUndefined(defValue) && result.push(defValue); |
|||
}); |
|||
return result.join(', '); |
|||
export default class Layers extends Collection { |
|||
initialize(p, opts = {}) { |
|||
this.prop = opts.prop; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
Layers.prototype.model = Layer; |
|||
|
|||
@ -1,101 +1,324 @@ |
|||
import { isString, isUndefined, keys } from 'underscore'; |
|||
import Property from './Property'; |
|||
import Properties from './Properties'; |
|||
|
|||
export default Property.extend({ |
|||
defaults: { |
|||
...Property.prototype.defaults, |
|||
// 'background' is a good example where to make a difference
|
|||
// between detached and not
|
|||
//
|
|||
// - NOT detached (default)
|
|||
// background: url(..) no-repeat center ...;
|
|||
// - Detached
|
|||
// background-image: url();
|
|||
// background-repeat: repeat;
|
|||
// ...
|
|||
detached: 0, |
|||
|
|||
// Array of sub properties
|
|||
properties: [], |
|||
|
|||
// Separator between properties
|
|||
separator: ' ' |
|||
}, |
|||
export const isNumberType = type => type === 'integer' || type === 'number'; |
|||
|
|||
/** |
|||
* |
|||
* [Property]: property.html |
|||
* |
|||
* |
|||
* @typedef PropertyComposite |
|||
* @property {Array<Object>} properties Array of sub properties, eg. `[{ type: 'number', property: 'margin-top' }, ...]` |
|||
* @property {Boolean} [detached=false] Indicate if the final CSS property is splitted (detached: `margin-top: X; margin-right: Y; ...`) or combined (not detached: `margin: X Y ...;`) |
|||
* @property {String|RegExp} [separator=' '] Value used to split property values, default `" "`. |
|||
* @property {String} [join=' '] Value used to join property values, default `" "`. |
|||
* @property {Function} [fromStyle] Custom logic for getting property values from the target style object. |
|||
* \n |
|||
* ```js
|
|||
* fromStyle: (style) => { |
|||
* const margins = parseMarginShorthand(style.margin); |
|||
* return { |
|||
* 'margin-top': margins.top, |
|||
* // ...
|
|||
* }; |
|||
* } |
|||
* ``` |
|||
* @property {Function} [toStyle] Custom logic for creating the CSS style object to apply on selected targets. |
|||
* \n |
|||
* ```js
|
|||
* toStyle: (values) => { |
|||
* const top = values['margin-top'] || 0; |
|||
* const right = values['margin-right'] || 0; |
|||
* // ...
|
|||
* return { |
|||
* margin: `${top} ${right} ...`, |
|||
* }; |
|||
* } |
|||
* ``` |
|||
*/ |
|||
export default class PropertyComposite extends Property { |
|||
defaults() { |
|||
return { |
|||
...Property.getDefaults(), |
|||
detached: false, |
|||
properties: [], |
|||
separator: ' ', |
|||
join: null, |
|||
fromStyle: null, |
|||
toStyle: null, |
|||
full: true, |
|||
}; |
|||
} |
|||
|
|||
initialize(props = {}, opts = {}) { |
|||
Property.callParentInit(Property, this, props, opts); |
|||
const properties = this.get('properties') || []; |
|||
const Properties = require('./Properties').default; |
|||
this.set('properties', new Properties(properties)); |
|||
this.listenTo(this, 'change:value', this.updateValues); |
|||
const { em } = this; |
|||
const properties = new Properties(this.get('properties') || [], { em, parentProp: this }); |
|||
this.set('properties', properties, { silent: 1 }); |
|||
this.listenTo(properties, 'change', this.__upProperties); |
|||
Property.callInit(this, props, opts); |
|||
}, |
|||
} |
|||
|
|||
/** |
|||
* Clear the value |
|||
* @return {this} |
|||
* Get properties. |
|||
* @returns {Array<[Property]>} |
|||
*/ |
|||
clearValue(opts = {}) { |
|||
this.get('properties').each(property => property.clearValue()); |
|||
return Property.prototype.clearValue.apply(this, arguments); |
|||
}, |
|||
getProperties() { |
|||
return [...this.get('properties').models]; |
|||
} |
|||
|
|||
/** |
|||
* Update property values |
|||
* Get property by id. |
|||
* @param {String} id Property id. |
|||
* @returns {[Property]|null} |
|||
*/ |
|||
updateValues() { |
|||
const values = this.getFullValue().split(this.getSplitSeparator()); |
|||
this.get('properties').each((property, i) => { |
|||
const len = values.length; |
|||
// Try to get value from a shorthand:
|
|||
// 11px -> 11px 11px 11px 11xp
|
|||
// 11px 22px -> 11px 22px 11px 22xp
|
|||
const value = |
|||
values[i] || values[(i % len) + (len != 1 && len % 2 ? 1 : 0)]; |
|||
// There some issue with UndoManager
|
|||
//property.setValue(value, 0, {fromParent: 1});
|
|||
}); |
|||
}, |
|||
getProperty(id) { |
|||
return this.get('properties').filter(prop => prop.getId() === id || prop.getName() === id)[0] || null; |
|||
} |
|||
|
|||
/** |
|||
* Split by sperator but avoid it inside parenthesis |
|||
* @return {RegExp} |
|||
* Get property at index. |
|||
* @param {Number} index |
|||
* @returns {[Property]|null} |
|||
*/ |
|||
getSplitSeparator() { |
|||
return new RegExp(`${this.get('separator')}(?![^\\(]*\\))`); |
|||
}, |
|||
getPropertyAt(index) { |
|||
return this.get('properties').at(index); |
|||
} |
|||
|
|||
/** |
|||
* Check if the property is detached. |
|||
* @returns {Boolean} |
|||
*/ |
|||
isDetached() { |
|||
return !!this.get('detached'); |
|||
} |
|||
|
|||
/** |
|||
* Get current values of properties. |
|||
* @param {Object} [opts={}] Options |
|||
* @param {Boolean} [opts.byName=false] Use property names as a key instead of the id. |
|||
* @returns {Object} |
|||
* @example |
|||
* // In case the property is `margin` with sub properties like `margin-top`, `margin-right`, etc.
|
|||
* console.log(property.getValues()); |
|||
* // { 'margin-top': '10px', 'margin-right': '20px', ... };
|
|||
*/ |
|||
getValues({ byName } = {}) { |
|||
return this.getProperties().reduce((res, prop) => { |
|||
const key = byName ? prop.getName() : prop.getId(); |
|||
res[key] = `${prop.__getFullValue()}`; |
|||
return res; |
|||
}, {}); |
|||
} |
|||
|
|||
/** |
|||
* Get property separator. |
|||
* @returns {RegExp} |
|||
*/ |
|||
getSeparator() { |
|||
return this.getSplitSeparator(); |
|||
} |
|||
|
|||
/** |
|||
* Get the join value. |
|||
* @returns {String} |
|||
*/ |
|||
getJoin() { |
|||
return this.__getJoin(); |
|||
} |
|||
|
|||
/** |
|||
* Returns default value |
|||
* @param {Boolean} defaultProps Force to get defaults from properties |
|||
* @return {string} |
|||
* Get style object from current properties |
|||
* @param {Object} [opts={}] Options |
|||
* @param {Boolean} [opts.camelCase] Return property names in camelCase. |
|||
* @returns {Object} Style object |
|||
* @private |
|||
*/ |
|||
getDefaultValue(defaultProps) { |
|||
let value = this.get('defaults'); |
|||
getStyleFromProps(opts = {}) { |
|||
const name = this.getName(); |
|||
const join = this.__getJoin(); |
|||
const toStyle = this.get('toStyle'); |
|||
let values = this.getValues(); |
|||
let style = {}; |
|||
|
|||
if (value && !defaultProps) { |
|||
return value; |
|||
if (toStyle) { |
|||
style = toStyle(values, { join, name, property: this }); |
|||
} else { |
|||
values = this.getValues({ byName: true }); |
|||
|
|||
if (this.isDetached()) { |
|||
style = values; |
|||
} else { |
|||
const value = this.getProperties() |
|||
.map(p => p.__getFullValue({ withDefault: 1 })) |
|||
.filter(Boolean) |
|||
.join(join); |
|||
style = { [name]: value }; |
|||
} |
|||
} |
|||
|
|||
value = ''; |
|||
const properties = this.get('properties'); |
|||
properties.each((prop, index) => (value += `${prop.getDefaultValue()} `)); |
|||
return value.trim(); |
|||
}, |
|||
if (this.isDetached()) { |
|||
style[name] = ''; |
|||
} else { |
|||
style[name] = style[name] || ''; |
|||
style = { |
|||
...style, |
|||
...this.getProperties().reduce((acc, prop) => { |
|||
acc[prop.getName()] = ''; |
|||
return acc; |
|||
}, {}), |
|||
}; |
|||
} |
|||
|
|||
getFullValue() { |
|||
if (this.get('detached')) { |
|||
return ''; |
|||
return opts.camelCase |
|||
? Object.keys(style).reduce((res, key) => { |
|||
res[camelCase(key)] = style[key]; |
|||
return res; |
|||
}, {}) |
|||
: style; |
|||
} |
|||
|
|||
getSplitSeparator() { |
|||
return new RegExp(`${this.get('separator')}(?![^\\(]*\\))`); |
|||
} |
|||
|
|||
__upProperties(p, opts = {}) { |
|||
if (opts.__up || opts.__clearIn) return; |
|||
|
|||
const parentProp = this.__getParentProp(); |
|||
if (parentProp) return parentProp.__upProperties(this, opts); |
|||
|
|||
this.__upTargetsStyleProps(opts, p); |
|||
} |
|||
|
|||
__upTargetsStyleProps(opts = {}, prop) { |
|||
let style = this.getStyleFromProps(); |
|||
|
|||
if (this.isDetached() && prop) { |
|||
const name = prop.getName(); |
|||
style = { [name]: style[name] }; |
|||
} |
|||
|
|||
return this.get('properties').getFullValue(); |
|||
}, |
|||
this.__upTargetsStyle(style, opts); |
|||
} |
|||
|
|||
/** |
|||
* Get property at some index |
|||
* @param {Number} index |
|||
* @return {Object} |
|||
*/ |
|||
getPropertyAt(index) { |
|||
return this.get('properties').at(index); |
|||
_up(props, opts = {}) { |
|||
this.__setProperties(this.__getSplitValue(props.value), opts); |
|||
return Property.prototype._up.call(this, props, opts); |
|||
} |
|||
|
|||
getStyle(opts) { |
|||
return this.getStyleFromProps(opts); |
|||
} |
|||
|
|||
__getFullValue(opts = {}) { |
|||
if (this.isDetached() || opts.__clear) return ''; |
|||
|
|||
return this.getStyleFromProps()[this.getName()] || ''; |
|||
} |
|||
|
|||
__getJoin() { |
|||
const join = this.get('join'); |
|||
return isString(join) ? join : this.get('separator'); |
|||
} |
|||
|
|||
__styleHasProps(style = {}) { |
|||
const name = this.getName(); |
|||
const props = this.getProperties(); |
|||
const nameProps = props.map(prop => prop.getName()); |
|||
const allNameProps = [name, ...nameProps]; |
|||
return allNameProps.some(prop => !isUndefined(style[prop]) && style[prop] !== ''); |
|||
} |
|||
|
|||
__splitValue(value, sep) { |
|||
return value |
|||
.split(sep) |
|||
.map(value => value.trim()) |
|||
.filter(Boolean); |
|||
} |
|||
|
|||
__splitStyleName(style, name, sep) { |
|||
return this.__splitValue(style[name] || '', sep); |
|||
} |
|||
|
|||
__getSplitValue(value = '', { byName } = {}) { |
|||
const props = this.getProperties(); |
|||
const props4Nums = props.length === 4 && props.every(prop => isNumberType(prop.getType())); |
|||
const values = this.__splitValue(value, this.getSplitSeparator()); |
|||
const result = {}; |
|||
|
|||
props.forEach((prop, i) => { |
|||
const value = values[i]; |
|||
let res = !isUndefined(value) ? value : ''; // : prop.getDefaultValue();
|
|||
|
|||
if (props4Nums) { |
|||
// Try to get value from a shorthand:
|
|||
// 11px -> 11px 11px 11px 11xp
|
|||
// 11px 22px -> 11px 22px 11px 22xp
|
|||
const len = values.length; |
|||
res = values[i] || values[(i % len) + (len != 1 && len % 2 ? 1 : 0)] || res; |
|||
} |
|||
|
|||
const key = byName ? prop.getName() : prop.getId(); |
|||
result[key] = res || ''; |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
__getPropsFromStyle(style = {}, opts = {}) { |
|||
if (!this.__styleHasProps(style)) return null; |
|||
|
|||
const { byName } = opts; |
|||
const props = this.getProperties(); |
|||
const sep = this.getSplitSeparator(); |
|||
const fromStyle = this.get('fromStyle'); |
|||
let result = fromStyle ? fromStyle(style, { property: this, separator: sep }) : {}; |
|||
|
|||
if (!fromStyle) { |
|||
// Get props from the main property
|
|||
result = this.__getSplitValue(style[this.getName()] || '', { byName }); |
|||
|
|||
// Get props from the inner properties
|
|||
props.forEach(prop => { |
|||
const value = style[prop.getName()]; |
|||
const key = byName ? prop.getName() : prop.getId(); |
|||
if (!isUndefined(value) && value !== '') result[key] = value; |
|||
}); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
__setProperties(values = {}, opts = {}) { |
|||
this.getProperties().forEach(prop => { |
|||
const value = values[prop.getId()]; |
|||
prop.__getFullValue() !== value && prop.upValue(value, opts); |
|||
}); |
|||
|
|||
// Keep track of the values, otherwise clear() will not trigger changes.
|
|||
const valuesStr = keys(values) |
|||
.map(k => values[k]) |
|||
.join(' '); |
|||
this.set('value', valuesStr, { silent: true }); |
|||
} |
|||
|
|||
clear() { |
|||
this.getProperties().map(p => p.clear({ __clearIn: !this.isDetached() })); |
|||
return Property.prototype.clear.call(this); |
|||
} |
|||
|
|||
hasValue(opts) { |
|||
return this.getProperties().some(prop => prop.hasValue(opts)); |
|||
} |
|||
|
|||
getFullValue() { |
|||
return this.__getFullValue(); |
|||
} |
|||
|
|||
__canClearProp(prop) { |
|||
return this.isDetached() && prop.hasValue({ noParent: true }); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@ -1,76 +0,0 @@ |
|||
import { isUndefined } from 'underscore'; |
|||
import Property from './Property'; |
|||
import InputNumber from 'domain_abstract/ui/InputNumber'; |
|||
import { hasWin } from 'utils/mixins'; |
|||
|
|||
export default Property.extend({ |
|||
defaults: { |
|||
...Property.prototype.defaults, |
|||
// Array of units, eg. ['px', '%']
|
|||
units: [], |
|||
|
|||
// Selected unit, eg. 'px'
|
|||
unit: '', |
|||
|
|||
// Integer value steps
|
|||
step: 1, |
|||
|
|||
// Minimum value
|
|||
min: '', |
|||
|
|||
// Maximum value
|
|||
max: '' |
|||
}, |
|||
|
|||
/** |
|||
* Get property units. |
|||
* @returns {Array<String>} |
|||
*/ |
|||
getUnits() { |
|||
return this.get('units') || []; |
|||
}, |
|||
|
|||
/** |
|||
* Get property unit value. |
|||
* @returns {String} |
|||
*/ |
|||
getValueUnit() { |
|||
return this.get('unit'); |
|||
}, |
|||
|
|||
initialize(props = {}, opts = {}) { |
|||
Property.callParentInit(Property, this, props, opts); |
|||
const unit = this.get('unit'); |
|||
const units = this.get('units'); |
|||
this.input = hasWin() && new InputNumber({ model: this }); |
|||
|
|||
if (units.length && !unit) { |
|||
this.set('unit', units[0]); |
|||
} |
|||
Property.callInit(this, props, opts); |
|||
}, |
|||
|
|||
clearValue(opts = {}) { |
|||
this.set({ value: undefined, unit: undefined }, opts); |
|||
return this; |
|||
}, |
|||
|
|||
parseValue(val) { |
|||
const parsed = Property.prototype.parseValue.apply(this, arguments); |
|||
const { value, unit } = this.input.validateInputValue(parsed.value, { |
|||
deepCheck: 1 |
|||
}); |
|||
parsed.value = value; |
|||
parsed.unit = unit; |
|||
return parsed; |
|||
}, |
|||
|
|||
getFullValue() { |
|||
let value = this.get('value'); |
|||
let unit = this.get('unit'); |
|||
value = !isUndefined(value) ? value : ''; |
|||
unit = !isUndefined(unit) && value ? unit : ''; |
|||
value = `${value}${unit}`; |
|||
return Property.prototype.getFullValue.apply(this, [value]); |
|||
} |
|||
}); |
|||
@ -0,0 +1,116 @@ |
|||
import { isUndefined } from 'underscore'; |
|||
import Property from './Property'; |
|||
import InputNumber from 'domain_abstract/ui/InputNumber'; |
|||
import { hasWin } from 'utils/mixins'; |
|||
|
|||
/** |
|||
* @typedef PropertyNumber |
|||
* @property {Array<String>} units Array of units, eg. `['px', '%']` |
|||
* @property {Number} min Minimum value. |
|||
* @property {Number} max Maximum value. |
|||
* @property {Number} step Step value. |
|||
* |
|||
*/ |
|||
export default class PropertyNumber extends Property { |
|||
defaults() { |
|||
return { |
|||
...Property.getDefaults(), |
|||
units: [], |
|||
unit: '', |
|||
min: '', |
|||
max: '', |
|||
step: 1, |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Get property units. |
|||
* @returns {Array<String>} |
|||
*/ |
|||
getUnits() { |
|||
return this.get('units') || []; |
|||
} |
|||
|
|||
/** |
|||
* Get property unit value. |
|||
* @returns {String} |
|||
*/ |
|||
getUnit() { |
|||
return this.get('unit'); |
|||
} |
|||
|
|||
/** |
|||
* Get min value. |
|||
* @returns {Number} |
|||
*/ |
|||
getMin() { |
|||
return this.get('min'); |
|||
} |
|||
|
|||
/** |
|||
* Get max value. |
|||
* @returns {Number} |
|||
*/ |
|||
getMax() { |
|||
return this.get('max'); |
|||
} |
|||
|
|||
/** |
|||
* Get step value. |
|||
* @returns {Number} |
|||
*/ |
|||
getStep() { |
|||
return this.get('step'); |
|||
} |
|||
|
|||
/** |
|||
* Update property unit value. |
|||
* The change is also propagated to the selected targets. |
|||
* @param {String} unit New unit value |
|||
* @param {Object} [opts={}] Options |
|||
* @param {Boolean} [opts.noTarget=false] If `true` the change won't be propagated to selected targets. |
|||
* @returns {String} |
|||
*/ |
|||
upUnit(unit, opts) { |
|||
return this._up({ unit }, opts); |
|||
} |
|||
|
|||
initialize(props = {}, opts = {}) { |
|||
Property.callParentInit(Property, this, props, opts); |
|||
const unit = this.get('unit'); |
|||
const units = this.get('units'); |
|||
this.input = hasWin() && new InputNumber({ model: this }); |
|||
|
|||
if (units.length && !unit) { |
|||
this.set('unit', units[0], { silent: 1 }); |
|||
} |
|||
Property.callInit(this, props, opts); |
|||
} |
|||
|
|||
__getClearProps() { |
|||
return { |
|||
...Property.prototype.__getClearProps(), |
|||
unit: '', |
|||
}; |
|||
} |
|||
|
|||
parseValue(val, opts = {}) { |
|||
const parsed = Property.prototype.parseValue.apply(this, arguments); |
|||
const { value, unit } = this.input.validateInputValue(parsed.value, { |
|||
deepCheck: 1, |
|||
...opts, |
|||
}); |
|||
parsed.value = value; |
|||
parsed.unit = unit; |
|||
return parsed; |
|||
} |
|||
|
|||
getFullValue() { |
|||
let value = this.get('value'); |
|||
let unit = this.get('unit'); |
|||
value = !isUndefined(value) ? value : ''; |
|||
unit = !isUndefined(unit) && value ? unit : ''; |
|||
value = `${value}${unit}`; |
|||
return Property.prototype.getFullValue.apply(this, [value]); |
|||
} |
|||
} |
|||
@ -1,37 +1,10 @@ |
|||
import Property from './Property'; |
|||
|
|||
export default Property.extend({ |
|||
defaults: () => ({ |
|||
...Property.prototype.defaults, |
|||
// Array of options, eg. [{name: 'Label ', value: '100'}]
|
|||
options: [], |
|||
full: 1 |
|||
}), |
|||
|
|||
initialize(...args) { |
|||
Property.prototype.initialize.apply(this, args); |
|||
this.listenTo(this, 'change:options', this.onOptionChange); |
|||
}, |
|||
|
|||
onOptionChange() { |
|||
this.set('list', this.get('options')); |
|||
}, |
|||
|
|||
getOptions() { |
|||
const { options, list } = this.attributes; |
|||
return options && options.length ? options : list; |
|||
}, |
|||
|
|||
setOptions(opts = []) { |
|||
this.set('options', opts); |
|||
return this; |
|||
}, |
|||
|
|||
addOption(opt) { |
|||
if (opt) { |
|||
const opts = this.getOptions(); |
|||
this.setOptions([...opts, opt]); |
|||
} |
|||
return this; |
|||
import Property from './PropertySelect'; |
|||
|
|||
export default class PropertyRadio extends Property { |
|||
defaults() { |
|||
return { |
|||
...Property.getDefaults(), |
|||
full: 1, |
|||
}; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@ -1,8 +1,99 @@ |
|||
import Property from './PropertyRadio'; |
|||
|
|||
export default Property.extend({ |
|||
defaults: () => ({ |
|||
...Property.prototype.defaults(), |
|||
full: 0 |
|||
}) |
|||
}); |
|||
import Property from './Property'; |
|||
import { isDef } from 'utils/mixins'; |
|||
import { isString } from 'underscore'; |
|||
|
|||
/** |
|||
* @typedef PropertySelect |
|||
* @property {Array<Object>} options Array of option definitions. |
|||
* \n |
|||
* ```js
|
|||
* options: [ |
|||
* { id: '100', label: 'Set 100' }, |
|||
* { id: '200', label: 'Set 200' }, |
|||
* ] |
|||
* ``` |
|||
*/ |
|||
export default class PropertySelect extends Property { |
|||
defaults() { |
|||
return { |
|||
...Property.getDefaults(), |
|||
options: [], |
|||
full: 0, |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Get available options. |
|||
* @returns {Array<Object>} Array of options |
|||
*/ |
|||
getOptions() { |
|||
// support old list property
|
|||
const { options, list } = this.attributes; |
|||
return (options && options.length ? options : list) || []; |
|||
} |
|||
|
|||
/** |
|||
* Get current selected option or by id. |
|||
* @param {String} [id] Option id. |
|||
* @returns {Object | null} |
|||
*/ |
|||
getOption(id) { |
|||
const idSel = id || this.getValue(); |
|||
return this.getOptions().filter(o => this.getOptionId(o) === idSel)[0] || null; |
|||
} |
|||
|
|||
/** |
|||
* Update options. |
|||
* @param {Array<Object>} value New array of options, eg. `[{ id: 'val-1', label: 'Value 1' }]` |
|||
*/ |
|||
setOptions(value = []) { |
|||
this.set('options', value); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Add new option. |
|||
* @param {Object} value Option object, eg. `{ id: 'val-1', label: 'Value 1' }` |
|||
*/ |
|||
addOption(value) { |
|||
if (value) { |
|||
const opts = this.getOptions(); |
|||
this.setOptions([...opts, value]); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Get the option id from the option object. |
|||
* @param {Object} option Option object |
|||
* @returns {String} Option id |
|||
*/ |
|||
getOptionId(option) { |
|||
return isDef(option.id) ? option.id : option.value; |
|||
} |
|||
|
|||
/** |
|||
* Get option label. |
|||
* @param {String|Object} id Option id or the option object |
|||
* @param {Object} [opts={}] Options |
|||
* @param {Boolean} [opts.locale=true] Use the locale string from i18n module |
|||
* @returns {String} Option label |
|||
*/ |
|||
getOptionLabel(id, opts = {}) { |
|||
const { locale = true } = opts; |
|||
const option = (isString(id) ? this.getOption(id) : id) || {}; |
|||
const optId = this.getOptionId(option); |
|||
const label = option.label || option.name || optId; |
|||
const propId = this.getId(); |
|||
return (locale && this.em?.t(`styleManager.options.${propId}.${optId}`)) || label; |
|||
} |
|||
|
|||
initialize(...args) { |
|||
Property.prototype.initialize.apply(this, args); |
|||
this.listenTo(this, 'change:options', this.__onOptionChange); |
|||
} |
|||
|
|||
__onOptionChange() { |
|||
this.set('list', this.get('options')); |
|||
} |
|||
} |
|||
|
|||
@ -1,8 +1,10 @@ |
|||
import Property from './PropertyInteger'; |
|||
import Property from './PropertyNumber'; |
|||
|
|||
export default Property.extend({ |
|||
defaults: { |
|||
...Property.prototype.defaults, |
|||
showInput: 1 |
|||
export default class PropertySlider extends Property { |
|||
defaults() { |
|||
return { |
|||
...Property.getDefaults(), |
|||
showInput: 1, |
|||
}; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@ -1,89 +1,480 @@ |
|||
import { keys } from 'underscore'; |
|||
import Property from './PropertyComposite'; |
|||
import { keys, isUndefined, isArray, isString, isNumber } from 'underscore'; |
|||
import PropertyComposite, { isNumberType } from './PropertyComposite'; |
|||
import PropertyBase from './Property'; |
|||
import Layers from './Layers'; |
|||
import { camelCase } from 'utils/mixins'; |
|||
|
|||
export default Property.extend({ |
|||
defaults: { |
|||
...Property.prototype.defaults, |
|||
// Array of layers (which contain properties)
|
|||
layers: [], |
|||
const VALUES_REG = /,(?![^\(]*\))/; |
|||
const PARTS_REG = /\s(?![^(]*\))/; |
|||
|
|||
// The separator used to join layer values
|
|||
layerSeparator: ', ', |
|||
|
|||
// Prepend new layers in the list
|
|||
prepend: 0, |
|||
|
|||
// Layer preview
|
|||
preview: 0 |
|||
}, |
|||
/** |
|||
* |
|||
* [Layer]: layer.html |
|||
* |
|||
* |
|||
* @typedef PropertyStack |
|||
* @property {Boolean} [preview=false] Indicate if the layer should display a preview. |
|||
* @property {String|RegExp} [layerSeparator=', '] The separator used to split layer values. |
|||
* @property {String} [layerJoin=', '] Value used to join layer values. |
|||
* @property {Function} [layerLabel] Custom logic for creating layer labels. |
|||
* \n |
|||
* ```js
|
|||
* layerLabel: (layer) => { |
|||
* const values = layer.getValues(); |
|||
* return `A: ${values['prop-a']} B: ${values['prop-b']}`; |
|||
* } |
|||
* ``` |
|||
* |
|||
*/ |
|||
export default class PropertyStack extends PropertyComposite { |
|||
defaults() { |
|||
return { |
|||
...PropertyComposite.getDefaults(), |
|||
layers: [], |
|||
layerSeparator: ', ', |
|||
layerJoin: '', |
|||
prepend: 0, |
|||
preview: false, |
|||
layerLabel: null, |
|||
selectedLayer: null, |
|||
}; |
|||
} |
|||
|
|||
initialize(props = {}, opts = {}) { |
|||
Property.callParentInit(Property, this, props, opts); |
|||
PropertyComposite.callParentInit(PropertyComposite, this, props, opts); |
|||
const layers = this.get('layers'); |
|||
const layersColl = new Layers(layers); |
|||
const layersColl = new Layers(layers, { prop: this }); |
|||
layersColl.property = this; |
|||
layersColl.properties = this.get('properties'); |
|||
this.set('layers', layersColl); |
|||
Property.callInit(this, props, opts); |
|||
}, |
|||
this.set('layers', layersColl, { silent: true }); |
|||
this.on('change:selectedLayer', this.__upSelected); |
|||
this.listenTo(layersColl, 'add remove', this.__upLayers); |
|||
PropertyComposite.callInit(this, props, opts); |
|||
} |
|||
|
|||
/** |
|||
* Get all available layers. |
|||
* @returns {Array<[Layer]>} |
|||
*/ |
|||
getLayers() { |
|||
return this.__getLayers().models; |
|||
} |
|||
|
|||
__getLayers() { |
|||
return this.get('layers'); |
|||
}, |
|||
|
|||
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; |
|||
} |
|||
|
|||
/** |
|||
* Get layer by index. |
|||
* @param {Number} [index=0] Layer index position. |
|||
* @returns {[Layer]|null} |
|||
* @example |
|||
* // Get the first layer
|
|||
* const layerFirst = property.getLayer(0); |
|||
* // Get the last layer
|
|||
* const layers = this.getLayers(); |
|||
* const layerLast = property.getLayer(layers.length - 1); |
|||
*/ |
|||
getLayer(index = 0) { |
|||
return this.__getLayers().at(index) || null; |
|||
} |
|||
|
|||
/** |
|||
* Get selected layer. |
|||
* @returns {[Layer] | null} |
|||
*/ |
|||
getSelectedLayer() { |
|||
const layer = this.get('selectedLayer'); |
|||
return layer && layer.getIndex() >= 0 ? layer : null; |
|||
} |
|||
|
|||
/** |
|||
* Select layer. |
|||
* Without a selected layer any update made on inner properties has no effect. |
|||
* @param {[Layer]} layer Layer to select |
|||
* @example |
|||
* const layer = property.getLayer(0); |
|||
* property.selectLayer(layer); |
|||
*/ |
|||
selectLayer(layer) { |
|||
return this.set('selectedLayer', layer, { __select: true }); |
|||
} |
|||
|
|||
/** |
|||
* Select layer by index. |
|||
* @param {Number} index Index of the layer to select. |
|||
* @example |
|||
* property.selectLayerAt(1); |
|||
*/ |
|||
selectLayerAt(index = 0) { |
|||
const layer = this.getLayer(index); |
|||
return layer && this.selectLayer(layer); |
|||
} |
|||
|
|||
/** |
|||
* Move layer by index. |
|||
* @param {[Layer]} layer Layer to move. |
|||
* @param {Number} index New layer index. |
|||
* @example |
|||
* const layer = property.getLayer(1); |
|||
* property.moveLayer(layer, 0); |
|||
*/ |
|||
moveLayer(layer, index = 0) { |
|||
const currIndex = layer ? layer.getIndex() : -1; |
|||
|
|||
if (currIndex >= 0 && isNumber(index) && index >= 0 && index < this.getLayers().length && currIndex !== index) { |
|||
this.removeLayer(layer); |
|||
this.__getLayers().add(layer, { at: index }); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Add new layer to the stack. |
|||
* @param {Object} [props={}] Custom property values to use in a new layer. |
|||
* @param {Object} [opts={}] Options |
|||
* @param {Number} [opts.at] Position index (by default the layer will be appended at the end). |
|||
* @returns {[Layer]} Added layer. |
|||
* @example |
|||
* // Add new layer at the beginning of the stack with custom values
|
|||
* property.addLayer({ 'sub-prop1': 'value1', 'sub-prop2': 'value2' }, { at: 0 }); |
|||
*/ |
|||
addLayer(props = {}, opts = {}) { |
|||
const values = {}; |
|||
this.getProperties().forEach(prop => { |
|||
const key = prop.getId(); |
|||
const value = props[key]; |
|||
values[key] = isUndefined(value) ? prop.getDefaultValue() : value; |
|||
}); |
|||
const layer = this.get('layers').push({ values }, opts); |
|||
|
|||
return layer; |
|||
} |
|||
|
|||
return !detached |
|||
? style[property] |
|||
: keys(validStyles).length |
|||
? validStyles |
|||
: ''; |
|||
}, |
|||
/** |
|||
* Remove layer. |
|||
* @param {[Layer]} layer Layer to remove. |
|||
* @returns {[Layer]} Removed layer |
|||
* @example |
|||
* const layer = property.getLayer(0); |
|||
* property.removeLayer(layer); |
|||
*/ |
|||
removeLayer(layer) { |
|||
return this.get('layers').remove(layer); |
|||
} |
|||
|
|||
/** |
|||
* This method allows to customize layers returned from the target |
|||
* @param {Object} target |
|||
* @return {Array} Should return an array of layers |
|||
* Remove layer by index. |
|||
* @param {Number} index Index of the layer to remove |
|||
* @returns {[Layer]|null} Removed layer |
|||
* @example |
|||
* // return example
|
|||
* [ |
|||
* { |
|||
* properties: [ |
|||
* { property: 'width', ... } |
|||
* { property: 'height', ... } |
|||
* ] |
|||
* } |
|||
* ] |
|||
* property.removeLayerAt(0); |
|||
*/ |
|||
removeLayerAt(index = 0) { |
|||
const layer = this.getLayer(index); |
|||
return layer ? this.removeLayer(layer) : null; |
|||
} |
|||
|
|||
/** |
|||
* Get the layer label. The label can be customized with the `layerLabel` property. |
|||
* @param {[Layer]} layer |
|||
* @returns {String} |
|||
* @example |
|||
* const layer = this.getLayer(1); |
|||
* const label = this.getLayerLabel(layer); |
|||
*/ |
|||
getLayerLabel(layer) { |
|||
let result = ''; |
|||
|
|||
if (layer) { |
|||
const layerLabel = this.get('layerLabel'); |
|||
const values = layer.getValues(); |
|||
const index = layer.getIndex(); |
|||
|
|||
if (layerLabel) { |
|||
result = layerLabel(layer, { index, values, property: this }); |
|||
} else { |
|||
const parts = []; |
|||
this.getProperties().map(prop => { |
|||
parts.push(values[prop.getId()]); |
|||
}); |
|||
result = parts.filter(Boolean).join(' '); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* Get style object from the layer. |
|||
* @param {[Layer]} layer |
|||
* @param {Object} [opts={}] Options |
|||
* @param {Boolean} [opts.camelCase] Return property names in camelCase. |
|||
* @param {Object} [opts.number] Limit the result of the number types, eg. `number: { min: -3, max: 3 }` |
|||
* @returns {Object} Style object |
|||
*/ |
|||
getStyleFromLayer(layer, opts = {}) { |
|||
const join = this.__getJoin(); |
|||
const joinLayers = this.__getJoinLayers(); |
|||
const toStyle = this.get('toStyle'); |
|||
const name = this.getName(); |
|||
const values = layer.getValues(); |
|||
let style; |
|||
|
|||
if (toStyle) { |
|||
style = toStyle(values, { join, joinLayers, name, layer, property: this }); |
|||
} else { |
|||
const result = this.getProperties().map(prop => { |
|||
const name = prop.getName(); |
|||
const val = values[prop.getId()]; |
|||
let value = isUndefined(val) ? prop.getDefaultValue() : val; |
|||
|
|||
// Limit number values if necessary (useful for previews)
|
|||
if (opts.number && isNumberType(prop.getType())) { |
|||
const newVal = prop.parseValue(val, opts.number); |
|||
value = `${newVal.value}${newVal.unit}`; |
|||
} |
|||
|
|||
return { name, value }; |
|||
}); |
|||
style = this.isDetached() |
|||
? result.reduce((acc, item) => { |
|||
acc[item.name] = item.value; |
|||
return acc; |
|||
}, {}) |
|||
: { |
|||
[this.getName()]: result.map(r => r.value).join(join), |
|||
}; |
|||
} |
|||
|
|||
return opts.camelCase |
|||
? Object.keys(style).reduce((res, key) => { |
|||
res[camelCase(key)] = style[key]; |
|||
return res; |
|||
}, {}) |
|||
: style; |
|||
} |
|||
|
|||
/** |
|||
* Get preview style object from the layer. |
|||
* If the property has `preview: false` the returned object will be empty. |
|||
* @param {[Layer]} layer |
|||
* @param {Object} [opts={}] Options. Same of `getStyleFromLayer` |
|||
* @returns {Object} Style object |
|||
*/ |
|||
getStylePreview(layer, opts = {}) { |
|||
let result = {}; |
|||
const preview = this.get('preview'); |
|||
|
|||
if (preview) { |
|||
result = this.getStyleFromLayer(layer, opts); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* Get layer separator. |
|||
* @return {RegExp} |
|||
*/ |
|||
getLayerSeparator() { |
|||
const sep = this.get('layerSeparator'); |
|||
return isString(sep) ? new RegExp(`${sep}(?![^\\(]*\\))`) : sep; |
|||
} |
|||
|
|||
__upProperties(prop, opts = {}) { |
|||
const layer = this.getSelectedLayer(); |
|||
if (!layer) return; |
|||
layer.upValues({ [prop.getId()]: prop.__getFullValue() }); |
|||
if (opts.__up) return; |
|||
this.__upTargetsStyleProps(opts); |
|||
} |
|||
|
|||
__upLayers(m, c, o) { |
|||
this.__upTargetsStyleProps(o || c); |
|||
} |
|||
|
|||
__upTargets(p, opts = {}) { |
|||
if (opts.__select) return; |
|||
return PropertyBase.prototype.__upTargets.call(this, p, opts); |
|||
} |
|||
|
|||
__upTargetsStyleProps(opts = {}) { |
|||
this.__upTargetsStyle(this.getStyleFromLayers(), opts); |
|||
} |
|||
|
|||
__upTargetsStyle(style, opts) { |
|||
return PropertyBase.prototype.__upTargetsStyle.call(this, style, opts); |
|||
} |
|||
|
|||
__upSelected({ noEvent } = {}, opts = {}) { |
|||
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 value = values[prop.getId()]; |
|||
prop.__getFullValue() !== value && prop.upValue(value, { ...opts, __up: true }); |
|||
}); |
|||
|
|||
!noEvent && sm.__trgEv(sm.events.layerSelect, { property: this }); |
|||
} |
|||
|
|||
_up(props, opts = {}) { |
|||
const { __layers = [], ...rest } = props; |
|||
// Detached props will update their layers later in sm.__upProp
|
|||
!this.isDetached() && this.__setLayers(__layers); |
|||
this.__upSelected({ noEvent: true }, opts); |
|||
return PropertyBase.prototype._up.call(this, rest, opts); |
|||
} |
|||
|
|||
__setLayers(newLayers = []) { |
|||
const layers = this.__getLayers(); |
|||
const layersNew = newLayers.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 }); |
|||
} |
|||
|
|||
__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; |
|||
}, {}); |
|||
} |
|||
|
|||
__getLayersFromStyle(style = {}) { |
|||
if (!this.__styleHasProps(style)) return null; |
|||
|
|||
const name = this.getName(); |
|||
const props = this.getProperties(); |
|||
const sep = this.getLayerSeparator(); |
|||
const fromStyle = this.get('fromStyle'); |
|||
let result = fromStyle ? fromStyle(style, { property: this, separatorLayers: sep }) : []; |
|||
|
|||
if (!fromStyle) { |
|||
// Get layers from the main property
|
|||
const layers = this.__splitStyleName(style, name, sep) |
|||
.map(value => value.split(this.getSplitSeparator())) |
|||
.map(parts => { |
|||
const result = {}; |
|||
props.forEach((prop, i) => { |
|||
const value = parts[i]; |
|||
result[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue(); |
|||
}); |
|||
return result; |
|||
}); |
|||
// Get layers from the inner properties
|
|||
props.forEach(prop => { |
|||
const id = prop.getId(); |
|||
this.__splitStyleName(style, prop.getName(), sep) |
|||
.map(value => ({ [id]: value || prop.getDefaultValue() })) |
|||
.forEach((inLayer, i) => { |
|||
layers[i] = layers[i] ? { ...layers[i], ...inLayer } : inLayer; |
|||
}); |
|||
}); |
|||
result = layers; |
|||
} |
|||
|
|||
return isArray(result) ? result : [result]; |
|||
} |
|||
|
|||
getStyle(opts) { |
|||
return this.getStyleFromLayers(opts); |
|||
} |
|||
|
|||
getStyleFromLayers(opts) { |
|||
let result = {}; |
|||
const name = this.getName(); |
|||
const layers = this.getLayers(); |
|||
const props = this.getProperties(); |
|||
const styles = layers.map(l => this.getStyleFromLayer(l, opts)); |
|||
styles.forEach(style => { |
|||
keys(style).map(key => { |
|||
if (!result[key]) result[key] = []; |
|||
result[key].push(style[key]); |
|||
}); |
|||
}); |
|||
keys(result).map(key => { |
|||
result[key] = result[key].join(this.__getJoinLayers()); |
|||
}); |
|||
|
|||
if (this.isDetached()) { |
|||
result[name] = ''; |
|||
!layers.length && |
|||
props.map(prop => { |
|||
result[prop.getName()] = ''; |
|||
}); |
|||
} else { |
|||
const style = props.reduce((acc, prop) => { |
|||
acc[prop.getName()] = ''; |
|||
return acc; |
|||
}, {}); |
|||
result[name] = result[name] || ''; |
|||
result = { ...result, ...style }; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
__getJoinLayers() { |
|||
const join = this.get('layerJoin'); |
|||
const sep = this.get('layerSeparator'); |
|||
|
|||
return join || (isString(sep) ? sep : join); |
|||
} |
|||
|
|||
__getFullValue() { |
|||
if (this.get('detached')) return ''; |
|||
const style = this.getStyleFromLayers(); |
|||
|
|||
return style[this.getName()]; |
|||
} |
|||
|
|||
/** |
|||
* Extended |
|||
* @private |
|||
*/ |
|||
getLayersFromTarget(target) { |
|||
return; |
|||
hasValue(opts = {}) { |
|||
const { noParent } = opts; |
|||
const parentValue = noParent && this.getParentTarget(); |
|||
return this.getLayers().length > 0 && !parentValue; |
|||
} |
|||
|
|||
/** |
|||
* Extended |
|||
* @private |
|||
*/ |
|||
clear(opts = {}) { |
|||
this.__getLayers().reset(); |
|||
this.__upTargetsStyleProps(opts); |
|||
return PropertyBase.prototype.clear.call(this); |
|||
} |
|||
|
|||
__canClearProp() { |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@ -1,188 +1,120 @@ |
|||
import { isString, each } from 'underscore'; |
|||
import Backbone from 'backbone'; |
|||
import PropertiesView from './PropertiesView'; |
|||
|
|||
export default Backbone.View.extend({ |
|||
events: { |
|||
click: 'active', |
|||
'click [data-close-layer]': 'removeItem', |
|||
'mousedown [data-move-layer]': 'initSorter', |
|||
'touchstart [data-move-layer]': 'initSorter' |
|||
}, |
|||
import { View } from 'backbone'; |
|||
import { keys } from 'underscore'; |
|||
|
|||
export default class LayerView extends View { |
|||
events() { |
|||
return { |
|||
click: 'select', |
|||
'click [data-close-layer]': 'removeItem', |
|||
'mousedown [data-move-layer]': 'initSorter', |
|||
'touchstart [data-move-layer]': 'initSorter', |
|||
}; |
|||
} |
|||
|
|||
template(model) { |
|||
template() { |
|||
const { pfx, ppfx, em } = this; |
|||
const label = `${em && em.t('styleManager.layer')} ${model.get('index')}`; |
|||
const icons = em?.getConfig('icons'); |
|||
const iconClose = icons?.close || ''; |
|||
const iconMove = icons?.move || ''; |
|||
|
|||
return ` |
|||
<div id="${pfx}move" class="${ppfx}no-touch-actions" data-move-layer> |
|||
<i class="fa fa-arrows"></i> |
|||
</div> |
|||
<div id="${pfx}label">${label}</div> |
|||
<div id="${pfx}preview-box"> |
|||
<div id="${pfx}preview" data-preview></div> |
|||
</div> |
|||
<div id="${pfx}close-layer" class="${pfx}btn-close" data-close-layer> |
|||
⨯ |
|||
<div class="${pfx}label-wrp"> |
|||
<div id="${pfx}move" class="${ppfx}no-touch-actions" data-move-layer> |
|||
${iconMove} |
|||
</div> |
|||
<div id="${pfx}label" data-label></div> |
|||
<div id="${pfx}preview-box" class="${pfx}layer-preview" style="display: none" data-preview-box> |
|||
<div id="${pfx}preview" class="${pfx}layer-preview-cnt" data-preview></div> |
|||
</div> |
|||
<div id="${pfx}close-layer" class="${pfx}btn-close" data-close-layer> |
|||
${iconClose} |
|||
</div> |
|||
</div> |
|||
<div id="${pfx}inputs" data-properties></div> |
|||
<div style="clear:both"></div> |
|||
`;
|
|||
}, |
|||
} |
|||
|
|||
initialize(o = {}) { |
|||
let model = this.model; |
|||
this.stackModel = o.stackModel; |
|||
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; |
|||
const { model } = this; |
|||
const config = o.config || {}; |
|||
this.em = config.em; |
|||
this.config = config; |
|||
this.sorter = o.sorter; |
|||
this.pfx = config.stylePrefix || ''; |
|||
this.ppfx = config.pStylePrefix || ''; |
|||
this.propertyView = o.propertyView; |
|||
const 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); |
|||
this.listenTo(pModel, 'change:selectedLayer', this.updateVisibility); |
|||
|
|||
// For the sorter
|
|||
model.view = this; |
|||
model.set({ droppable: 0, draggable: 1 }); |
|||
this.$el.data('model', model); |
|||
}, |
|||
} |
|||
|
|||
/** |
|||
* Delegate sorting |
|||
* @param {Event} e |
|||
* */ |
|||
initSorter(e) { |
|||
if (this.sorter) this.sorter.startSort(this.el); |
|||
}, |
|||
initSorter() { |
|||
this.sorter?.startSort(this.el); |
|||
} |
|||
|
|||
removeItem(ev) { |
|||
ev && ev.stopPropagation(); |
|||
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); |
|||
this.model.remove(); |
|||
} |
|||
|
|||
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(';')); |
|||
} |
|||
} |
|||
}, |
|||
select() { |
|||
this.model.select(); |
|||
} |
|||
|
|||
getPropertiesWrapper() { |
|||
if (!this.propsWrapEl) { |
|||
this.propsWrapEl = this.el.querySelector('[data-properties]'); |
|||
} |
|||
if (!this.propsWrapEl) this.propsWrapEl = this.el.querySelector('[data-properties]'); |
|||
return this.propsWrapEl; |
|||
}, |
|||
} |
|||
|
|||
getPreviewEl() { |
|||
if (!this.previewEl) { |
|||
this.previewEl = this.el.querySelector('[data-preview]'); |
|||
} |
|||
if (!this.previewEl) this.previewEl = this.el.querySelector('[data-preview]'); |
|||
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)); |
|||
}, |
|||
updateLabel() { |
|||
const { model } = this; |
|||
const label = model.getLabel(); |
|||
this.getLabelEl().innerHTML = label; |
|||
|
|||
if (model.hasPreview()) { |
|||
const prvEl = this.getPreviewEl(); |
|||
const style = model.getStylePreview({ number: { min: -3, max: 3 } }); |
|||
const styleStr = keys(style) |
|||
.map(k => `${k}:${style[k]}`) |
|||
.join(';'); |
|||
prvEl.setAttribute('style', styleStr); |
|||
} |
|||
} |
|||
|
|||
updateVisibility() { |
|||
const pfx = this.pfx; |
|||
const { pfx, model, propertyView } = this; |
|||
const wrapEl = this.getPropertiesWrapper(); |
|||
const active = this.model.get('active'); |
|||
wrapEl.style.display = active ? '' : 'none'; |
|||
this.$el[active ? 'addClass' : 'removeClass'](`${pfx}active`); |
|||
}, |
|||
const isSelected = model.isSelected(); |
|||
wrapEl.style.display = isSelected ? '' : 'none'; |
|||
this.$el[isSelected ? 'addClass' : 'removeClass'](`${pfx}active`); |
|||
isSelected && wrapEl.appendChild(propertyView.props.el); |
|||
} |
|||
|
|||
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.className = `${pfx}layer${!preview ? ` ${pfx}no-preview` : ''}`; |
|||
this.props = properties; |
|||
this.getPropertiesWrapper().appendChild(propsEl); |
|||
const { el, pfx, model } = this; |
|||
el.innerHTML = this.template(); |
|||
el.className = `${pfx}layer`; |
|||
if (model.hasPreview()) { |
|||
el.querySelector(`[data-preview-box]`).style.display = ''; |
|||
} |
|||
this.updateLabel(); |
|||
this.updateVisibility(); |
|||
this.updatePreview(); |
|||
return this; |
|||
} |
|||
}); |
|||
} |
|||
|
|||