Browse Source

Merge pull request #487 from artf/dev

v0.12.25
pull/883/head
Artur Arseniev 9 years ago
committed by GitHub
parent
commit
479711717b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 77
      README.md
  3. 2
      dist/css/grapes.min.css
  4. 6
      dist/grapes.min.js
  5. 1381
      index.html
  6. 12195
      package-lock.json
  7. 23
      package.json
  8. 5
      src/asset_manager/config/config.js
  9. 2
      src/asset_manager/view/AssetsView.js
  10. 22
      src/block_manager/view/BlockView.js
  11. 6
      src/canvas/index.js
  12. 36
      src/canvas/view/CanvasView.js
  13. 4
      src/commands/view/Resize.js
  14. 81
      src/commands/view/SelectComponent.js
  15. 5
      src/css_composer/index.js
  16. 4
      src/css_composer/model/CssRule.js
  17. 10
      src/css_composer/model/CssRules.js
  18. 334
      src/demo.js
  19. 180
      src/dom_components/model/Component.js
  20. 45
      src/dom_components/model/ComponentLink.js
  21. 15
      src/dom_components/model/ComponentSvg.js
  22. 6
      src/dom_components/model/ComponentText.js
  23. 9
      src/dom_components/model/Components.js
  24. 9
      src/dom_components/view/ComponentTextView.js
  25. 14
      src/domain_abstract/model/Styleable.js
  26. 59
      src/domain_abstract/ui/Input.js
  27. 39
      src/domain_abstract/ui/InputColor.js
  28. 139
      src/domain_abstract/ui/InputNumber.js
  29. 5
      src/editor/index.js
  30. 27
      src/editor/model/Editor.js
  31. 1
      src/editor/view/EditorView.js
  32. 13
      src/panels/index.js
  33. 1
      src/panels/model/Button.js
  34. 34
      src/panels/model/Buttons.js
  35. 20
      src/panels/view/ButtonView.js
  36. 17
      src/parser/model/ParserHtml.js
  37. 14
      src/rich_text_editor/index.js
  38. 29
      src/selector_manager/index.js
  39. 28
      src/storage_manager/model/RemoteStorage.js
  40. 9
      src/style_manager/config/config.js
  41. 44
      src/style_manager/index.js
  42. 2
      src/style_manager/model/Layers.js
  43. 54
      src/style_manager/model/Property.js
  44. 25
      src/style_manager/model/PropertyComposite.js
  45. 24
      src/style_manager/model/PropertyFactory.js
  46. 17
      src/style_manager/model/PropertyInteger.js
  47. 6
      src/style_manager/model/PropertySlider.js
  48. 4
      src/style_manager/model/Sector.js
  49. 2
      src/style_manager/model/Sectors.js
  50. 60
      src/style_manager/view/PropertiesView.js
  51. 7
      src/style_manager/view/PropertyCompositeView.js
  52. 8
      src/style_manager/view/PropertyIntegerView.js
  53. 5
      src/style_manager/view/PropertyRadioView.js
  54. 14
      src/style_manager/view/PropertySliderView.js
  55. 80
      src/style_manager/view/PropertyView.js
  56. 13
      src/style_manager/view/SectorsView.js
  57. 1
      src/styles/scss/_gjs_canvas.scss
  58. 78
      src/styles/scss/_gjs_inputs.scss
  59. 4
      src/styles/scss/main.scss
  60. 15
      src/trait_manager/config/config.js
  61. 26
      src/trait_manager/index.js
  62. 15
      src/trait_manager/model/TraitFactory.js
  63. 35
      src/trait_manager/model/Traits.js
  64. 175
      src/utils/Resizer.js
  65. 190
      src/utils/Sorter.js
  66. 8
      src/utils/extender.js
  67. 3
      src/utils/fetch.js
  68. 24
      src/utils/mixins.js
  69. 3
      test/specs/css_composer/e2e/CssComposer.js
  70. 193
      test/specs/dom_components/model/Component.js
  71. 9
      test/specs/panels/index.js
  72. 22
      test/specs/panels/model/PanelModels.js
  73. 31
      test/specs/panels/view/ButtonView.js
  74. 28
      test/specs/parser/model/ParserHtml.js
  75. 31
      test/specs/selector_manager/index.js
  76. 87
      test/specs/style_manager/model/Models.js
  77. 16
      test/specs/style_manager/view/PropertyIntegerView.js
  78. 4
      test/specs/style_manager/view/PropertyRadioView.js
  79. 7
      test/specs/style_manager/view/PropertyView.js
  80. 13
      webpack.config.js
  81. 6293
      yarn.lock

1
.gitignore

@ -15,3 +15,4 @@ coverage/
node_modules/
bower_components/
grapesjs-*.tgz
_index.html

77
README.md

@ -31,9 +31,9 @@ Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
## Table of contents
* [Features](#features)
* [Installation](#installation)
* [Development](#development)
* [Download](#download)
* [Usage](#usage)
* [Development](#development)
* [Documentation](#documentation)
* [API](#api)
* [Testing](#testing)
@ -70,40 +70,21 @@ Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
## Installation
* `npm i grapesjs` / `yarn add grapesjs`
* `git clone https://github.com/artf/grapesjs.git`
For development purpose you should follow instructions below.
## Development
GrapesJS uses [Webpack](https://github.com/webpack/webpack) as a module bundler and [Babel](https://github.com/babel/babel) as a compiler.
Clone the repository and install all the necessary dependencies
```sh
$ git clone https://github.com/artf/grapesjs.git
$ cd grapesjs
$ npm i
```
## Download
Start the dev server
* CDNs
* UNPKG
* `https://unpkg.com/grapesjs`
* `https://unpkg.com/grapesjs/dist/css/grapes.min.css`
* CDNJS
* `https://cdnjs.cloudflare.com/ajax/libs/grapesjs/0.12.17/grapes.min.js`
* `https://cdnjs.cloudflare.com/ajax/libs/grapesjs/0.12.17/css/grapes.min.css`
* NPM
* `npm i grapesjs`
* GIT
* `git clone https://github.com/artf/grapesjs.git`
```sh
$ npm start
```
Build before the commit. This will also increase the patch level version of the package
```sh
$ npm run build
```
For the development purpose you should follow instructions below.
@ -148,6 +129,30 @@ For more practical example I suggest to look up the code inside this demo: http:
## Development
GrapesJS uses [Webpack](https://github.com/webpack/webpack) as a module bundler and [Babel](https://github.com/babel/babel) as a compiler.
Clone the repository and install all the necessary dependencies
```sh
$ git clone https://github.com/artf/grapesjs.git
$ cd grapesjs
$ npm i
```
Start the dev server
```sh
$ npm start
```
Once the development server is started you should be able to reach the demo page (eg. `http://localhost:8080`)
## Documentation
Check the getting started guide here: [wiki]
@ -205,9 +210,13 @@ If you like the project support it with a donation of your choice or become a ba
<a href="https://opencollective.com/grapesjs/sponsors/0/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/0/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/1/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/1/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/2/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/2/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/3/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/3/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/0/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/0/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/1/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/1/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/2/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/2/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/3/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/3/avatar"></a>

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

6
dist/grapes.min.js

File diff suppressed because one or more lines are too long

1381
index.html

File diff suppressed because it is too large

12195
package-lock.json

File diff suppressed because it is too large

23
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.12.8",
"version": "0.12.25",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
@ -25,13 +25,14 @@
"devDependencies": {
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"chai": "^4.1.2",
"cross-env": "^5.0.5",
"documentation": "^5.3.0",
"eslint": "^4.1.1",
"expect": "^1.20.2",
"html-webpack-plugin": "^2.30.1",
"istanbul": "^0.4.2",
"jsdom": "^11.2.0",
"mocha": "^3.1.2",
@ -55,8 +56,20 @@
],
"babel": {
"presets": [
"es2015",
"stage-3"
[
"env",
{
"targets": [
"> 1%",
"ie 11",
"safari 8"
],
"useBuiltIns": true
}
]
],
"plugins": [
"transform-object-rest-spread"
]
},
"scripts": {

5
src/asset_manager/config/config.js

@ -1,4 +1,4 @@
module.exports = {
module.exports = {
// Default assets
// eg. [
// 'https://...image1.png',
@ -76,4 +76,7 @@ module.exports = {
// Default title for the asset manager modal
modalTitle: 'Select Image',
//Default placeholder for input
inputPlaceholder: 'http://path/to/the/image.jpg'
};

2
src/asset_manager/view/AssetsView.js

@ -16,7 +16,7 @@ module.exports = Backbone.View.extend({
<div class="${pfx}assets-header">
<form class="${pfx}add-asset">
<div class="${ppfx}field ${pfx}add-field">
<input placeholder="http://path/to/the/image.jpg"/>
<input placeholder="${view.config.inputPlaceholder}"/>
</div>
<button class="${ppfx}btn-prim">${view.config.addBtnText}</button>
<div style="clear:both"></div>

22
src/block_manager/view/BlockView.js

@ -1,4 +1,4 @@
const $ = Backbone.$;
import {on, off} from 'utils/mixins';
module.exports = Backbone.View.extend({
@ -6,12 +6,11 @@ module.exports = Backbone.View.extend({
mousedown: 'startDrag'
},
initialize(o, config) {
_.bindAll(this, 'endDrag');
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
initialize(o, config = {}) {
this.config = config;
this.endDrag = this.endDrag.bind(this);
this.ppfx = config.pStylePrefix || '';
this.listenTo(this.model, 'destroy remove', this.remove);
this.doc = $(document);
},
/**
@ -33,7 +32,7 @@ module.exports = Backbone.View.extend({
sorter.setDragHelper(this.el, e);
sorter.startSort(this.el);
sorter.setDropContent(this.model.get('content'));
this.doc.on('mouseup', this.endDrag);
on(document, 'mouseup', this.endDrag);
},
/**
@ -41,7 +40,7 @@ module.exports = Backbone.View.extend({
* @private
*/
endDrag(e) {
this.doc.off('mouseup', this.endDrag);
off(document, 'mouseup', this.endDrag);
const sorter = this.config.getSorter();
// After dropping the block in the canvas the mouseup event is not yet
@ -53,9 +52,10 @@ module.exports = Backbone.View.extend({
},
render() {
var className = this.ppfx + 'block';
this.$el.addClass(className);
this.el.innerHTML = '<div class="' + className + '-label">' + this.model.get('label') + '</div>';
const el = this.el;
const className = `${this.ppfx}block`;
el.className += ` ${className}`;
el.innerHTML = `<div class="${className}-label">${this.model.get('label')}</div>`;
return this;
},

6
src/canvas/index.js

@ -206,11 +206,7 @@ module.exports = () => {
* @private
*/
offset(el) {
var rect = el.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
return CanvasView.offset(el);
},
/**

36
src/canvas/view/CanvasView.js

@ -16,6 +16,21 @@ module.exports = Backbone.View.extend({
});
},
/**
* Checks if the element is visible in the canvas's viewport
* @param {HTMLElement} el
* @return {Boolean}
*/
isElInViewport(el) {
const rect = el.getBoundingClientRect();
const frameRect = this.getFrameOffset(1);
const rTop = rect.top;
const rLeft = rect.left;
return rTop >= 0 && rLeft >= 0 &&
rTop <= frameRect.height && rLeft <= frameRect.width;
},
/**
* Update tools position
* @private
@ -106,13 +121,13 @@ module.exports = Backbone.View.extend({
var frameCss = `
${baseCss}
.${ppfx}dashed :not([contenteditable]) > *[data-highlightable] {
.${ppfx}dashed *[data-highlightable] {
outline: 1px dashed rgba(170,170,170,0.7);
outline-offset: -2px
outline-offset: -3px
}
.${ppfx}comp-selected {
outline: 3px solid #3b97e3 !important
outline: 3px solid #3b97e3 !important;
}
.${ppfx}comp-selected-parent {
@ -269,14 +284,13 @@ module.exports = Backbone.View.extend({
var frmTop = opt.avoidFrameOffset ? 0 : frmOff.top;
var frmLeft = opt.avoidFrameOffset ? 0 : frmOff.left;
var top = eo.top + frmTop - cvsOff.top;
var left = eo.left + frmLeft - cvsOff.left;
return {
top,
left,
height: el.offsetHeight,
width: el.offsetWidth
};
const top = eo.top + frmTop - cvsOff.top;
const left = eo.left + frmLeft - cvsOff.left;
// clientHeight/clientWidth are for SVGs
const height = el.offsetHeight || el.clientHeight;
const width = el.offsetWidth || el.clientWidth;
return { top, left, height, width };
},
/**

4
src/commands/view/Resize.js

@ -25,8 +25,8 @@ module.exports = {
},
stop() {
if(this.canvasResizer)
this.canvasResizer.blur();
const resizer = this.canvasResizer;
resizer && resizer.blur();
},
};

81
src/commands/view/SelectComponent.js

@ -1,4 +1,5 @@
import {on, off} from 'utils/mixins'
import { bindAll } from 'underscore';
import { on, off, getUnitFromValue} from 'utils/mixins';
const ToolbarView = require('dom_components/view/ToolbarView');
const Toolbar = require('dom_components/model/Toolbar');
@ -10,12 +11,12 @@ const $ = Backbone.$;
module.exports = {
init(o) {
_.bindAll(this, 'onHover', 'onOut', 'onClick', 'onKeyPress');
bindAll(this, 'onHover', 'onOut', 'onClick', 'onKeyPress',
'copyComp', 'pasteComp', 'onFrameScroll');
},
enable() {
_.bindAll(this, 'copyComp', 'pasteComp', 'onFrameScroll');
this.frameOff = this.canvasOff = this.adjScroll = null;
var config = this.config.em.get('Config');
this.startSelectComponent();
@ -25,7 +26,6 @@ module.exports = {
em.on('component:update', this.updateAttached, this);
em.on('change:canvasOffset', this.updateAttached, this);
em.on('change:selectedComponent', this.updateToolbar, this);
},
/**
@ -90,6 +90,7 @@ module.exports = {
* @private
* */
toggleSelectComponent(enable) {
const em = this.em;
const method = enable ? 'on' : 'off';
const methods = {on, off};
const body = this.getCanvasBody();
@ -99,6 +100,7 @@ module.exports = {
methods[method](body, 'click', this.onClick);
methods[method](win, 'scroll', this.onFrameScroll);
methods[method](win, 'keydown', this.onKeyPress);
em[method]('change:selectedComponent', this.onSelect, this);
},
/**
@ -221,18 +223,14 @@ module.exports = {
},
/**
* Hover command
* @param {Object} e
* On element click
* @param {Event} e
* @private
*/
onClick(e) {
var m = $(e.target).data('model');
if(!m)
return;
var s = m.get('stylable');
if(!(s instanceof Array) && !s)
return;
this.onSelect(e, e.target);
e.stopPropagation();
const model = $(e.target).data('model');
model && this.editor.select(model);
},
/**
@ -296,16 +294,19 @@ module.exports = {
* @param {Object} el
* @private
* */
onSelect(e, el) {
e.stopPropagation();
var model = $(el).data('model');
onSelect() {
const editor = this.editor;
const model = this.em.getSelected();
this.updateToolbar(model);
if (model) {
this.editor.select(model);
const el = model.view.el;
this.showFixedElementOffset(el);
this.hideElementOffset();
this.hideHighlighter();
this.initResize(el);
} else {
editor.stopCommand('resize');
}
},
@ -319,55 +320,79 @@ module.exports = {
var editor = em ? em.get('Editor') : '';
var config = em ? em.get('Config') : '';
var pfx = config.stylePrefix || '';
var attrName = 'data-' + pfx + 'handler';
var resizeClass = pfx + 'resizing';
var attrName = `data-${pfx}handler`;
var resizeClass = `${pfx}resizing`;
var model = em.get('selectedComponent');
var resizable = model.get('resizable');
var options = {};
var modelToStyle;
var toggleBodyClass = (method, e, opts) => {
opts.docs && opts.docs.find('body')[method](resizeClass);
const docs = opts.docs;
docs && docs.forEach(doc => {
const body = doc.body;
const cls = body.className || '';
body.className = (method == 'add' ?
`${cls} ${resizeClass}` : cls.replace(resizeClass, '')).trim();
});
};
if (editor && resizable) {
options = {
onStart(e, opts) {
toggleBodyClass('addClass', e, opts);
// Here the resizer is updated with the current element height and width
onStart(e, opts = {}) {
const { el, config, resizer } = opts;
const { keyHeight, keyWidth, currentUnit } = config;
toggleBodyClass('add', e, opts);
modelToStyle = em.get('StyleManager').getModelToStyle(model);
const computedStyle = getComputedStyle(el);
const modelStyle = modelToStyle.getStyle();
const currentWidth = modelStyle[keyWidth] || computedStyle[keyWidth];
const currentHeight = modelStyle[keyHeight] || computedStyle[keyHeight];
resizer.startDim.w = parseFloat(currentWidth);
resizer.startDim.h = parseFloat(currentHeight);
showOffsets = 0;
if (currentUnit) {
config.unitHeight = getUnitFromValue(currentHeight);
config.unitWidth = getUnitFromValue(currentWidth);
}
},
// Update all positioned elements (eg. component toolbar)
onMove() {
editor.trigger('change:canvasOffset');
},
onEnd(e, opts) {
toggleBodyClass('removeClass', e, opts);
toggleBodyClass('remove', e, opts);
editor.trigger('change:canvasOffset');
showOffsets = 1;
},
updateTarget(el, rect, options = {}) {
if (!modelToStyle) {
return;
}
const {store, selectedHandler} = options;
const { store, selectedHandler, config} = options;
const { keyHeight, keyWidth } = config;
const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler) >= 0;
const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler) >= 0;
const unit = 'px';
const style = modelToStyle.getStyle();
if (!onlyHeight) {
style.width = rect.w + unit;
style[keyWidth] = rect.w + config.unitWidth;
}
if (!onlyWidth) {
style.height = rect.h + unit;
style[keyHeight] = rect.h + config.unitHeight;
}
modelToStyle.setStyle(style, {avoidStore: 1});
em.trigger('targetStyleUpdated');
const updateEvent = `update:component:style`;
em && em.trigger(`${updateEvent}:height ${updateEvent}:width`);
if (store) {
modelToStyle.trigger('change:style', modelToStyle, style, {});

5
src/css_composer/index.js

@ -77,9 +77,8 @@ module.exports = () => {
var elStyle = (c.em && c.em.config.style) || '';
c.rules = elStyle || c.rules;
c.sm = c.em; // TODO Refactor
c.sm = c.em;
rules = new CssRules([], c);
rulesView = new CssRulesView({
collection: rules,
config: c,
@ -182,7 +181,7 @@ module.exports = () => {
opt.state = s;
opt.mediaText = w;
opt.selectors = '';
rule = new CssRule(opt);
rule = new CssRule(opt, c);
rule.get('selectors').add(selectors);
rules.add(rule);
return rule;

4
src/css_composer/model/CssRule.js

@ -25,9 +25,9 @@ module.exports = Backbone.Model.extend(Styleable).extend({
stylable: true,
},
initialize(c, opt) {
initialize(c, opt = {}) {
this.config = c || {};
const em = opt && opt.sm;
const em = opt.em;
let selectors = this.config.selectors || [];
this.em = em;

10
src/css_composer/model/CssRules.js

@ -4,12 +4,12 @@ var CssRule = require('./CssRule');
module.exports = Backbone.Collection.extend({
initialize(models, opt) {
// Inject editor
if(opt && opt.sm)
this.editor = opt.sm;
this.model = (attrs, options) => {
// Not used
this.model = (attrs, options) => {
var model;
if(!options.sm && opt && opt.sm)
@ -25,9 +25,11 @@ module.exports = Backbone.Collection.extend({
},
add(models, opt) {
if(typeof models === 'string')
add(models, opt = {}) {
if (typeof models === 'string') {
models = this.editor.get('Parser').parseCss(models);
}
opt.em = this.editor;
return Backbone.Collection.prototype.add.apply(this, [models, opt]);
},

334
src/demo.js

@ -1,334 +0,0 @@
require(['config/require-config'], function() {
require(['grapesjs/main'],function (grapesjs){
var editor = grapesjs.init(
{
noticeOnUnload: 0,
container : '#gjs',
height: '100%',
fromElement: true,
/*
components: [{
type: 'text',
style:{
width:'100px',
height:'100px',
margin: '50px auto',
},
traits: ['title'],
components: [{
type: 'textnode',
content: 'text node row',
},{
type: 'textnode',
content: ', another text node',
},{
type: 'link',
content: 'someLink',
},{
type: 'textnode',
content: " More text node --- ",
}],
}],*/
storageManager:{
autoload: 0,
storeComponents: 1,
storeStyles: 1,
},
commands: {
defaults : [{
id: 'open-github',
run: function(editor, sender){
sender.set('active',false);
window.open('https://github.com/artf/grapesjs','_blank');
}
},{
id: 'undo',
run: function(editor, sender){
sender.set('active',false);
editor.UndoManager.undo(true);
}
},{
id: 'redo',
run: function(editor, sender){
sender.set('active',false);
editor.UndoManager.redo(true);
}
},{
id: 'clean-all',
run: function(editor, sender){
sender.set('active',false);
if(confirm('Are you sure to clean the canvas?')){
var comps = editor.DomComponents.clear();
}
}
}],
},
assetManager: {
storageType : '',
storeOnChange : true,
storeAfterUpload : true,
assets : [
{ type: 'image', src : 'http://placehold.it/350x250/78c5d6/fff/image1.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/459ba8/fff/image2.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/79c267/fff/image3.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/c5d647/fff/image4.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/f28c33/fff/image5.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/e868a2/fff/image6.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/cc4360/fff/image7.jpg', height:350, width:250},
{ type: 'image', src : './img/work-desk.jpg', date: '2015-02-01',height:1080, width:1728},
{ type: 'image', src : './img/phone-app.png', date: '2015-02-01',height:650, width:320},
{ type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728},
]
},
styleManager : {
sectors: [{
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'],
},{
name: 'Dimension',
open: false,
buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
},{
name: 'Typography',
open: false,
buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-align', 'text-shadow'],
properties: [{
property: 'text-align',
list : [
{value: 'left', className: 'fa fa-align-left'},
{value: 'center', className: 'fa fa-align-center' },
{value: 'right', className: 'fa fa-align-right'},
{value: 'justify', className: 'fa fa-align-justify'}
],
}]
},{
name: 'Decorations',
open: false,
buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'],
},{
name: 'Extra',
open: false,
buildProps: ['transition', 'perspective', 'transform'],
},{
name: 'Dimension',
open: false,
buildProps: ['margin'],
properties:[{
name: 'Marginnnn',
property: 'margin',
type: 'composite',
properties:[{
name: 'Top',
property: 'margin-top',
},{
name: 'Right',
property: 'margin-right',
},{
name: 'Bottom',
property: 'margin-bottom',
},{
name: 'Left',
property: 'margin-left',
},],
}/*{
name : 'Center blocksss',
property : 'margins',
type : 'select',
defaults : '0',
list : [{
value : '0',
name : 'Normal',
},{
value : '0 auto',
name : 'Center',
}],
}*/],
},{
name: 'Flex',
open: false,
properties: [{
name : 'Flex Container',
property : 'display',
type : 'select',
defaults : 'block',
list : [{
value : 'block',
name : 'Disable',
},{
value : 'flex',
name : 'Enable',
}],
},{
name: 'Flex Parent',
property: 'label-parent-flex',
},{
name : 'Direction',
property : 'flex-direction',
type : 'radio',
defaults : 'row',
list : [{
value : 'row',
name : 'Row',
className : 'icons-flex icon-dir-row',
title : 'Row',
},{
value : 'row-reverse',
name : 'Row reverse',
className : 'icons-flex icon-dir-row-rev',
title : 'Row reverse',
},{
value : 'column',
name : 'Column',
title : 'Column',
className : 'icons-flex icon-dir-col',
},{
value : 'column-reverse',
name : 'Column reverse',
title : 'Column reverse',
className : 'icons-flex icon-dir-col-rev',
}],
},{
name : 'Wrap',
property : 'flex-wrap',
type : 'radio',
defaults : 'nowrap',
list : [{
value : 'nowrap',
title : 'Single line',
},{
value : 'wrap',
title : 'Multiple lines',
},{
value : 'wrap-reverse',
title : 'Multiple lines reverse',
}],
},{
name : 'Justify',
property : 'justify-content',
type : 'radio',
defaults : 'flex-start',
list : [{
value : 'flex-start',
className : 'icons-flex icon-just-start',
title : 'Start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-just-end',
},{
value : 'space-between',
title : 'Space between',
className : 'icons-flex icon-just-sp-bet',
},{
value : 'space-around',
title : 'Space around',
className : 'icons-flex icon-just-sp-ar',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-just-sp-cent',
}],
},{
name : 'Align',
property : 'align-items',
type : 'radio',
defaults : 'center',
list : [{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
},{
name: 'Flex Children',
property: 'label-parent-flex',
},{
name: 'Order',
property: 'order',
type: 'integer',
defaults : 0,
min: 0
},{
name : 'Flex',
property : 'flex',
type : 'composite',
properties : [{
name: 'Grow',
property: 'flex-grow',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Shrink',
property: 'flex-shrink',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Basis',
property: 'flex-basis',
type: 'integer',
units: ['px','%',''],
unit: '',
defaults : 'auto',
}],
},{
name : 'Align',
property : 'align-self',
type : 'radio',
defaults : 'auto',
list : [{
value : 'auto',
name : 'Auto',
},{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
}]
}
],
},
}
);
window.editor = editor;
});
});

180
src/dom_components/model/Component.js

@ -1,3 +1,4 @@
import { isUndefined, isArray } from 'underscore';
import Styleable from 'domain_abstract/model/Styleable';
var Backbone = require('backbone');
@ -21,7 +22,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
// True if the component is removable from the canvas
removable: true,
// Indicates if it's possible to drag the component inside other
// Indicates if it's possible to drag the component inside others
// Tip: Indicate an array of selectors where it could be dropped inside
draggable: true,
@ -69,7 +70,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
icon: '',
// Component related style
style: {},
style: '',
// Key-value object of the component's attributes
attributes: '',
@ -83,6 +84,19 @@ module.exports = Backbone.Model.extend(Styleable).extend({
// Traits
traits: ['id', 'title'],
// Indicates an array of properties which will be inhereted by
// all NEW appended children
//
// If you create a model likes this
// removable: false,
// draggable: false,
// propagate: ['removable', 'draggable']
// When you append some new component inside, the new added model
// will get the exact same properties indicated in `propagate` array
// (as the `propagate` property itself)
//
propagate: '',
/**
* Set an array of items to show up inside the toolbar (eg. move, clone, delete)
* when the component is selected
@ -97,8 +111,26 @@ module.exports = Backbone.Model.extend(Styleable).extend({
toolbar: null,
},
initialize(props = {}, opt = {}) {
const em = opt.sm || {};
const em = opt.sm || opt.em || {};
// Propagate properties from parent if indicated
const parent = this.parent();
const parentAttr = parent && parent.attributes;
if (parentAttr && parentAttr.propagate) {
let newAttr = {};
const toPropagate = parentAttr.propagate;
toPropagate.forEach(prop => newAttr[prop] = parent.get(prop));
newAttr.propagate = toPropagate;
newAttr = {...newAttr, ...props};
this.set(newAttr);
}
const propagate = this.get('propagate');
propagate && this.set('propagate', isArray(propagate) ? propagate : [propagate]);
// Check void elements
if(opt && opt.config &&
@ -106,8 +138,10 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.set('void', true);
}
opt.em = em;
this.opt = opt;
this.sm = em;
this.em = em;
this.config = props;
this.set('attributes', this.get('attributes') || {});
this.listenTo(this, 'change:script', this.scriptUpdated);
@ -132,24 +166,148 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.init();
},
/**
* Update attributes of the model
* @param {Object} attrs Key value attributes
* @example
* model.setAttributes({id: 'test', 'data-key': 'value'});
*/
setAttributes(attrs) {
attrs = { ...attrs };
// Handle classes
const classes = attrs.class;
classes && this.setClass(classes);
delete attrs.class;
// Handle style
const style = attrs.style;
style && this.setStyle(style);
delete attrs.style;
this.set('attributes', attrs);
},
/**
* Return attributes
* @return {Object}
*/
getAttributes() {
return this.get('attributes');
},
/**
* Add classes
* @param {Array|string} classes Array or string of classes
* @return {Array} Array of added selectors
* @example
* model.addClass('class1');
* model.addClass('class1 class2');
* model.addClass(['class1', 'class2']);
* // -> [SelectorObject, ...]
*/
addClass(classes) {
const added = this.em.get('SelectorManager').addClass(classes);
return this.get('classes').add(added);
},
/**
* Set classes (resets current collection)
* @param {Array|string} classes Array or string of classes
* @return {Array} Array of added selectors
* @example
* model.setClass('class1');
* model.setClass('class1 class2');
* model.setClass(['class1', 'class2']);
* // -> [SelectorObject, ...]
*/
setClass(classes) {
this.get('classes').reset();
return this.addClass(classes);
},
initClasses() {
const classes = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.set('classes', new Selectors(classes));
return this;
},
initComponents() {
let comps = new Components(this.get('components'), this.opt);
// Have to add components after the init, otherwise the parent
// is not visible
const comps = new Components(null, this.opt);
comps.parent = this;
comps.reset(this.get('components'));
this.set('components', comps);
return this;
},
/**
* Initialize callback
*/
init() {},
/**
* Add new component children
* @param {Component|string} components Component to add
* @param {Object} [opts={}] Options, same as in `model.add()`(from backbone)
* @return {Array} Array of appended components
* @example
* someModel.get('components').lenght // -> 0
* const videoComponent = someModel.append('<video></video><div></div>')[0];
* // This will add 2 components (`video` and `div`) to your `someModel`
* someModel.get('components').lenght // -> 2
* // You can pass components directly
* otherModel.append(otherModel2);
* otherModel.append([otherModel3, otherModel4]);
*/
append(components, opts = {}) {
const result = this.components().add(components, opts);
return isArray(result) ? result : [result];
},
/**
* Set new collection if `components` are provided, otherwise the
* current collection is returned
* @param {Component|string} [components] Components to set
* @return {Collection|undefined}
* @example
* // Get current collection
* const collection = model.components();
* // Set new collection
* model.components('<span></span><div></div>');
*/
components(components) {
const coll = this.get('components');
if (isUndefined(components)) {
return coll;
} else {
coll.reset();
components && this.append(components);
}
},
/**
* Get parent model
* @return {Component}
*/
parent() {
const coll = this.collection;
return coll && coll.parent;
},
/**
* Script updated
*/
@ -157,6 +315,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.set('scriptUpdated', 1);
},
/**
* Once traits are updated I have to populates model's attributes
*/
@ -183,6 +342,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
found && this.set('attributes', attrs);
},
/**
* Init toolbar
*/
@ -218,13 +378,14 @@ module.exports = Backbone.Model.extend(Styleable).extend({
}
},
/**
* Load traits
* @param {Array} traits
* @private
*/
loadTraits(traits, opts = {}) {
var trt = new Traits();
var trt = new Traits([], this.opt);
trt.setTarget(this);
traits = traits || this.get('traits');
@ -236,6 +397,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return this;
},
/**
* Normalize input classes from array to array of objects
* @param {Array} arr
@ -266,6 +428,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return res;
},
/**
* Override original clone method
* @private
@ -299,6 +462,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return new this.constructor(attr, this.opt);
},
/**
* Get the name of the component
* @return {string}
@ -312,6 +476,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return customName || name;
},
/**
* Get the icon string
* @return {string}
@ -321,6 +486,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return icon ? icon + ' ' : '';
},
/**
* Return HTML string of the component
* @param {Object} opts Options
@ -369,6 +535,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return code;
},
/**
* Returns object of attributes for HTML
* @return {Object}
@ -380,6 +547,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return attr;
},
/**
* Return a shallow copy of the model's attributes for JSON
* stringification.
@ -398,6 +566,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return obj;
},
/**
* Return model id
* @return {string}
@ -407,6 +576,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
return attrs.id || this.cid;
},
/**
* Return script in string format, cleans 'function() {..' from scripts
* if it's a function

45
src/dom_components/model/ComponentLink.js

@ -1,12 +1,12 @@
var Component = require('./ComponentText');
const Component = require('./ComponentText');
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
defaults: { ...Component.prototype.defaults,
type: 'link',
tagName: 'a',
traits: ['title', 'href', 'target'],
}),
},
/**
* Returns object of attributes for HTML
@ -14,26 +14,39 @@ module.exports = Component.extend({
* @private
*/
getAttrToHTML(...args) {
var attr = Component.prototype.getAttrToHTML.apply(this, args);
const attr = Component.prototype.getAttrToHTML.apply(this, args);
delete attr.onmousedown;
return attr;
},
},{
}, {
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
if(el.tagName == 'A'){
result = {type: 'link'};
let result;
let avoidEdit;
if (el.tagName == 'A') {
result = {
type: 'link',
editable: 0,
};
// The link is editable only if, at least, one of its
// children is a text node (not empty one)
const children = el.childNodes;
const len = children.length;
if (!len) delete result.editable;
for (let i = 0; i < len; i++) {
const child = children[i];
if (child.nodeType == 3 && child.textContent.trim() != '') {
delete result.editable;
break;
}
}
}
return result;
},

15
src/dom_components/model/ComponentSvg.js

@ -2,6 +2,10 @@ var Component = require('./Component');
module.exports = Component.extend({
defaults: { ...Component.prototype.defaults,
highlightable: 0,
},
getName() {
let name = this.get('tagName');
let customName = this.get('custom-name');
@ -13,7 +17,16 @@ module.exports = Component.extend({
isComponent(el) {
if (SVGElement && el instanceof SVGElement) {
return {type: 'svg'};
// Some SVG elements require uppercase letters (eg. <linearGradient>)
const tagName = el.tagName;
// Make the root resizable
const resizable = tagName == 'svg' ? true : false;
return {
tagName,
type: 'svg',
resizable
};
}
},

6
src/dom_components/model/ComponentText.js

@ -1,11 +1,11 @@
var Component = require('./Component');
const Component = require('./Component');
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
defaults: { ...Component.prototype.defaults,
type: 'text',
droppable: false,
editable: true,
}),
},
});

9
src/dom_components/model/Components.js

@ -9,8 +9,8 @@ module.exports = Backbone.Collection.extend({
this.config = opt && opt.config ? opt.config : null;
// Inject editor
if(opt && opt.sm)
this.editor = opt.sm;
if(opt && (opt.sm || opt.em))
this.editor = opt.sm || opt.em;
this.model = (attrs, options) => {
var model;
@ -18,6 +18,9 @@ module.exports = Backbone.Collection.extend({
if(!options.sm && opt && opt.sm)
options.sm = opt.sm;
if(!options.em && opt && opt.em)
options.em = opt.em;
if(opt && opt.config)
options.config = opt.config;
@ -67,7 +70,7 @@ module.exports = Backbone.Collection.extend({
var style = model.get('style');
var em = this.editor;
if(!_.isEmpty(style) && em && em.get('Config').forceClass){
if (!_.isEmpty(style) && em && em.get && em.get('Config').forceClass) {
var cssC = this.editor.get('CssComposer');
var newClass = this.editor.get('SelectorManager').add(model.cid);
model.set({style:{}});

9
src/dom_components/view/ComponentTextView.js

@ -23,14 +23,13 @@ module.exports = ComponentView.extend({
* @private
* */
enableEditing() {
if (this.rteEnabled) {
const rte = this.rte;
if (this.rteEnabled || !this.model.get('editable')) {
return;
}
const editable = this.model.get('editable');
const rte = this.rte;
if (rte && editable) {
if (rte) {
try {
this.activeRte = rte.enable(this, this.activeRte);
} catch (err) {

14
src/domain_abstract/model/Styleable.js

@ -1,3 +1,7 @@
import { isString } from 'underscore';
import ParserHtml from 'parser/model/ParserHtml';
const parseStyle = ParserHtml().parseStyle;
export default {
/**
@ -20,11 +24,19 @@ export default {
/**
* Set new style object
* @param {Object} prop
* @param {Object|string} prop
* @param {Object} opts
*/
setStyle(prop = {}, opts = {}) {
if (isString(prop)) {
prop = parseStyle(prop);
}
this.set('style', Object.assign({}, prop), opts);
for (let pr in prop) {
this.trigger(`change:style:${pr}`);
}
},
/**

59
src/domain_abstract/ui/Input.js

@ -6,21 +6,30 @@ module.exports = Backbone.View.extend({
'change': 'handleChange',
},
template() {
const holderClass = this.holderClass;
return `<span class="${holderClass}"></span>`;
return `<span class="${this.holderClass()}"></span>`;
},
inputClass() {
return `${this.ppfx}field`;
},
holderClass() {
return `${this.ppfx}input-holder`;
},
initialize(opts = {}) {
const ppfx = opts.ppfx || '';
this.target = opts.target || {};
this.inputClass = ppfx + 'field';
this.inputHolderClass = ppfx + 'input-holder';
this.holderClass = `${ppfx}input-holder`;
this.opts = opts;
this.ppfx = ppfx;
this.target = opts.target || {};
this.listenTo(this.model, 'change:value', this.handleModelChange);
},
/**
* Fired when the element of the property is updated
*/
@ -28,27 +37,19 @@ module.exports = Backbone.View.extend({
this.model.trigger('el:change');
},
/**
* Handled when the view is changed
*/
handleChange(e) {
e.stopPropagation();
this.model.set('value', this.getInputEl().value);
this.elementUpdated();
},
/**
* Set value to the model
* Set value to the input element
* @param {string} value
* @param {Object} opts
*/
setValue(value, opts = {}) {
setValue(value) {
const model = this.model;
let val = value || model.get('defaults');
const input = this.getInputEl();
input && (input.value = val);
},
/**
* Updates the view when the model is changed
* */
@ -56,26 +57,36 @@ module.exports = Backbone.View.extend({
this.setValue(value, opts);
},
/**
* Handled when the view is changed
*/
handleChange(e) {
e.stopPropagation();
this.model.set('value', this.getInputEl().value);
this.elementUpdated();
},
/**
* Get the input element
* @return {HTMLElement}
*/
getInputEl() {
if(!this.inputEl) {
if (!this.inputEl) {
const plh = this.model.get('defaults');
const cls = this.inputCls;
this.inputEl = $(`<input type="text" class="${cls}" placeholder="${plh}">`);
this.inputEl = $(`<input type="text" placeholder="${plh}">`);
}
return this.inputEl.get(0);
},
render() {
const el = this.$el;
const ppfx = this.ppfx;
const holderClass = this.holderClass;
el.addClass(this.inputClass);
el.addClass(this.inputClass());
el.html(this.template());
el.find(`.${holderClass}`).append(this.getInputEl());
el.find(`.${this.holderClass()}`).append(this.getInputEl());
return this;
}

39
src/domain_abstract/ui/InputColor.js

@ -1,6 +1,5 @@
const Input = require('./Input');
//require('spectrum-colorpicker');
require('utils/ColorPicker');
const Input = require('./Input');
const $ = Backbone.$;
module.exports = Input.extend({
@ -8,21 +7,22 @@ module.exports = Input.extend({
template() {
const ppfx = this.ppfx;
return `
<div class="${ppfx}input-holder"></div>
<div class="${this.holderClass()}"></div>
<div class="${ppfx}field-colorp">
<div class="${ppfx}field-colorp-c">
<div class="${ppfx}field-colorp-c" data-colorp-c>
<div class="${ppfx}checker-bg"></div>
</div>
</div>
`;
},
initialize(opts) {
Input.prototype.initialize.apply(this, arguments);
inputClass() {
const ppfx = this.ppfx;
this.colorCls = `${ppfx}field-color-picker`;
this.inputClass = `${ppfx}field ${ppfx}field-color`;
this.colorHolderClass = `${ppfx}field-colorp-c`;
return `${ppfx}field ${ppfx}field-color`;
},
holderClass() {
return `${this.ppfx}input-holder`;
},
/**
@ -39,19 +39,13 @@ module.exports = Input.extend({
inputEl.value = value;
colorEl.get(0).style.backgroundColor = valueClr;
if (opts.targetUpdate) {
// This prevents from adding multiple thumbs in spectrum
if (opts.fromTarget) {
colorEl.spectrum('set', valueClr);
this.noneColor = value == 'none';
}
},
/**
* Updates the view when the model is changed
* */
handleModelChange(model, value, opts) {
this.setValue(value, opts);
},
/**
* Get the color input element
* @return {HTMLElement}
@ -61,7 +55,7 @@ module.exports = Input.extend({
const self = this;
var model = this.model;
var colorEl = $(`<div class="${this.colorCls}"></div>`);
var colorEl = $(`<div class="${this.ppfx}field-color-picker"></div>`);
var cpStyle = colorEl.get(0).style;
var elToAppend = this.target && this.target.config ? this.target.config.el : '';
const getColor = color => {
@ -71,7 +65,7 @@ module.exports = Input.extend({
let changed = 0;
let previousСolor;
this.$el.find(`.${this.colorHolderClass}`).append(colorEl);
this.$el.find(`[data-colorp-c]`).append(colorEl);
colorEl.spectrum({
appendTo: elToAppend || 'body',
@ -84,14 +78,13 @@ module.exports = Input.extend({
move(color) {
const cl = getColor(color);
cpStyle.backgroundColor = cl;
model.set('value', cl, {avoidStore: 1});
model.setValueFromInput(cl, 0);
},
change(color) {
changed = 1;
const cl = getColor(color);
cpStyle.backgroundColor = cl;
model.set('value', '', {avoidStore: 1});
model.set('value', cl);
model.setValueFromInput(cl);
self.noneColor = 0;
},
show(color) {
@ -105,7 +98,7 @@ module.exports = Input.extend({
}
cpStyle.backgroundColor = previousСolor;
colorEl.spectrum('set', previousСolor);
model.set('value', previousСolor, {avoidStore: 1});
model.setValueFromInput(previousСolor, 0);
}
}
});

139
src/domain_abstract/ui/InputNumber.js

@ -1,38 +1,46 @@
import {on, off} from 'utils/mixins'
import { bindAll } from 'underscore';
import {on, off} from 'utils/mixins';
const Input = require('./Input');
const Backbone = require('backbone');
const $ = Backbone.$;
module.exports = Backbone.View.extend({
module.exports = Input.extend({
template: _.template(`
<span class='<%= ppfx %>input-holder'></span>
<span class='<%= ppfx %>field-units'></span>
<div class="<%= ppfx %>field-arrows">
<div class="<%= ppfx %>field-arrow-u"></div>
<div class="<%= ppfx %>field-arrow-d"></div>
</div>`),
events: {
'change input': 'handleChange',
'change select': 'handleUnitChange',
'click [data-arrow-up]': 'upArrowClick',
'click [data-arrow-down]': 'downArrowClick',
'mousedown [data-arrows]': 'downIncrement',
},
initialize(opts) {
_.bindAll(this, 'moveIncrement', 'upIncrement');
var opt = opts || {};
var ppfx = opt.ppfx || '';
var contClass = opt.contClass || (`${ppfx}field ${ppfx}field-integer`);
this.ppfx = ppfx;
template() {
const ppfx = this.ppfx;
return `
<span class="${ppfx}input-holder"></span>
<span class="${ppfx}field-units"></span>
<div class="${ppfx}field-arrows" data-arrows>
<div class="${ppfx}field-arrow-u" data-arrow-up></div>
<div class="${ppfx}field-arrow-d" data-arrow-down></div>
</div>
`;
},
inputClass() {
const ppfx = this.ppfx;
return this.opts.contClass || `${ppfx}field ${ppfx}field-integer`;
},
initialize(opts = {}) {
Input.prototype.initialize.apply(this, arguments);
bindAll(this, 'moveIncrement', 'upIncrement');
this.doc = document;
this.inputCls = ppfx + 'field-number';
this.unitCls = ppfx + 'input-unit';
this.contClass = contClass;
this.events = {};
this.events[`click .${ppfx}field-arrow-u`] = 'upArrowClick';
this.events[`click .${ppfx}field-arrow-d`] = 'downArrowClick';
this.events[`mousedown .${ppfx}field-arrows`] = 'downIncrement';
this.events[`change .${this.inputCls}`] = 'handleChange';
this.events[`change .${this.unitCls}`] = 'handleUnitChange';
this.listenTo(this.model, 'change:unit change:value', this.handleModelChange);
this.delegateEvents();
this.listenTo(this.model, 'change:unit', this.handleModelChange);
},
/**
* Set value to the model
* @param {string} value
@ -57,6 +65,7 @@ module.exports = Backbone.View.extend({
}
},
/**
* Handled when the view is changed
*/
@ -66,6 +75,7 @@ module.exports = Backbone.View.extend({
this.elementUpdated();
},
/**
* Handled when the view is changed
*/
@ -76,6 +86,7 @@ module.exports = Backbone.View.extend({
this.elementUpdated();
},
/**
* Fired when the element of the property is updated
*/
@ -83,55 +94,45 @@ module.exports = Backbone.View.extend({
this.model.trigger('el:change');
},
/**
* Updates the view when the model is changed
* */
handleModelChange() {
var m = this.model;
this.getInputEl().value = m.get('value');
var unit = this.getUnitEl();
if (unit) {
unit.value = m.get('unit');
}
const model = this.model;
this.getInputEl().value = model.get('value');
const unitEl = this.getUnitEl();
unitEl && (unitEl.value = model.get('unit'));
},
/**
* Get the input element
* @return {HTMLElement}
*/
getInputEl() {
if (!this.inputEl) {
const cls = this.inputCls;
const plh = this.model.get('defaults');
this.inputEl = $(`<input type="text" class="${cls}" placeholder="${plh}">`);
}
return this.inputEl.get(0);
},
/**
* Get the unit element
* @return {HTMLElement}
*/
getUnitEl() {
if(!this.unitEl) {
var model = this.model;
var units = model.get('units') || [];
if(units.length){
var unitStr = '<select class="' + this.unitCls + '">';
_.each(units, unit => {
var selected = unit == model.get('unit') ? 'selected': '';
unitStr += '<option ' + selected + ' >' + unit + '</option>';
if (!this.unitEl) {
const model = this.model;
const units = model.get('units') || [];
if (units.length) {
const options = [];
units.forEach(unit => {
const selected = unit == model.get('unit') ? 'selected' : '';
options.push(`<option ${selected}>${unit}</option>`);
});
unitStr += '</select>';
const temp = document.createElement('div');
temp.innerHTML = unitStr;
temp.innerHTML = `<select class="${this.ppfx}input-unit">${options.join('')}</select>`;
this.unitEl = temp.firstChild;
}
}
return this.unitEl;
},
/**
* Invoked when the up arrow is clicked
* */
@ -145,19 +146,21 @@ module.exports = Backbone.View.extend({
this.elementUpdated();
},
/**
* Invoked when the down arrow is clicked
* */
downArrowClick() {
const model = this.model;
const step = model.get('step');
let value = model.get('value');
value = this.normalizeValue(value - step);
var valid = this.validateInputValue(value);
const value = model.get('value');
const val = this.normalizeValue(value - step);
var valid = this.validateInputValue(val);
model.set('value', valid.value);
this.elementUpdated();
},
/**
* Change easily integer input value with click&drag method
* @param Event
@ -174,6 +177,7 @@ module.exports = Backbone.View.extend({
on(this.doc, 'mouseup', this.upIncrement);
},
/** While the increment is clicked, moving the mouse will update input value
* @param Object
*
@ -190,6 +194,7 @@ module.exports = Backbone.View.extend({
return false;
},
/**
* Stop moveIncrement method
* */
@ -207,6 +212,7 @@ module.exports = Backbone.View.extend({
}
},
normalizeValue(value, defValue = 0) {
const model = this.model;
const step = model.get('step');
@ -219,12 +225,14 @@ module.exports = Backbone.View.extend({
value = parseFloat(value);
if (Math.floor(value) !== value) {
stepDecimals = step.toString().split('.')[1].length || 0;
const side = step.toString().split('.')[1];
stepDecimals = side ? side.length : 0;
}
return stepDecimals ? parseFloat(value.toFixed(stepDecimals)) : value;
},
/**
* Validate input value
* @param {String} value Raw value
@ -235,7 +243,7 @@ module.exports = Backbone.View.extend({
var force = 0;
var opt = opts || {};
var model = this.model;
var val = value || model.get('defaults');
var val = value !== '' ? value : model.get('defaults');
var units = model.get('units') || [];
var unit = model.get('unit') || (units.length && units[0]) || '';
var max = model.get('max');
@ -277,14 +285,11 @@ module.exports = Backbone.View.extend({
};
},
render() {
const ppfx = this.ppfx;
const el = this.$el;
el.html(this.template({ppfx}));
el.find(`.${ppfx}input-holder`).append(this.getInputEl());
Input.prototype.render.call(this);
const unit = this.getUnitEl();
unit && el.find(`.${ppfx}field-units`).get(0).appendChild(unit);
el.addClass(this.contClass);
unit && this.$el.find(`.${this.ppfx}field-units`).get(0).appendChild(unit);
return this;
}

5
src/editor/index.js

@ -89,6 +89,7 @@
* style: '.txt-red{color: red}',
* });
*/
import $ from 'cash-dom';
module.exports = config => {
var c = config || {},
@ -110,6 +111,8 @@ module.exports = config => {
return {
$,
/**
* @property {EditorModel}
* @private
@ -539,7 +542,7 @@ module.exports = config => {
* @return {this}
*/
trigger(event) {
return em.trigger(event);
return em.trigger.apply(em, arguments);
},
/**

27
src/editor/model/Editor.js

@ -13,11 +13,11 @@ const deps = [
require('style_manager'),
require('asset_manager'),
require('css_composer'),
require('trait_manager'),
require('dom_components'),
require('canvas'),
require('commands'),
require('block_manager'),
require('trait_manager'),
require('block_manager')
];
const Backbone = require('backbone');
@ -63,7 +63,7 @@ module.exports = Backbone.Model.extend({
this.initUndoManager();
this.on('change:selectedComponent', this.componentSelected, this);
this.on('change:changesCount', this.updateBeforeUnload, this);
this.on('change:changesCount', this.updateChanges, this);
},
/**
@ -100,21 +100,28 @@ module.exports = Backbone.Model.extend({
}
},
/**
* Set the alert before unload in case it's requested
* and there are unsaved changes
* @private
*/
updateBeforeUnload() {
var changes = this.get('changesCount');
updateChanges() {
const stm = this.get('StorageManager');
const changes = this.get('changesCount');
if (this.config.noticeOnUnload && changes) {
window.onbeforeunload = e => 1;
} else {
window.onbeforeunload = null;
}
if (stm.isAutosave() && changes >= stm.getStepsBeforeSave()) {
this.store();
}
},
/**
* Load generic module
* @param {String} moduleName Module name
@ -199,16 +206,8 @@ module.exports = Backbone.Model.extend({
timedInterval && clearInterval(timedInterval);
timedInterval = setTimeout(() => {
var count = this.get('changesCount') + 1;
var stm = this.get('StorageManager');
this.set('changesCount', count);
if (!stm.isAutosave() || count < stm.getStepsBeforeSave()) {
return;
}
if (!opt.avoidStore) {
this.store();
this.set('changesCount', this.get('changesCount') + 1, opt)
}
}, 0);
},

1
src/editor/view/EditorView.js

@ -9,6 +9,7 @@ module.exports = Backbone.View.extend({
this.pn = model.get('Panels');
model.on('loaded', () => {
this.pn.active();
this.pn.disableButtons();
model.runDefault();
setTimeout(() => model.trigger('load'), 0);
});

13
src/panels/index.js

@ -200,6 +200,19 @@ module.exports = () => {
});
});
},
/**
* Disable buttons flagged as disabled
* @private
*/
disableButtons() {
this.getPanels().each(p => {
p.get('buttons').each(btn => {
if(btn.get('disable'))
btn.trigger('change:disable');
});
});
},
Panel,

1
src/panels/model/Button.js

@ -14,6 +14,7 @@ module.exports = Backbone.Model.extend({
dragDrop: false,
runDefaultCommand: true,
stopDefaultCommand: false,
disable: false,
},
initialize(options) {

34
src/panels/model/Buttons.js

@ -38,5 +38,39 @@ module.exports = Backbone.Collection.extend({
}
});
},
/**
* Disables all buttons
* @param {String} ctx Context string
*
* @return void
* */
disableAllButtons(ctx) {
var context = ctx || '';
this.forEach((model, index) => {
if( model.get('context') == context ){
model.set('disable', true);
if(model.get('buttons').length)
model.get('buttons').disableAllButtons(context);
}
});
},
/**
* Disables all buttons, except one passed
* @param {Object} except Model to ignore
* @param {Boolean} r Recursive flag
*
* @return void
* */
disableAllButtonsExceptOne(except, r) {
this.forEach((model, index) => {
if(model !== except){
model.set('disable', true);
if(r && model.get('buttons').length)
model.get('buttons').disableAllButtonsExceptOne(except,r);
}
});
},
});

20
src/panels/view/ButtonView.js

@ -13,6 +13,7 @@ module.exports = Backbone.View.extend({
this.ppfx = this.config.pStylePrefix || '';
this.id = this.pfx + this.model.get('id');
this.activeCls = this.pfx + 'active';
this.disableCls = this.pfx + 'active';
this.btnsVisCls = this.pfx + 'visible';
this.parentM = o.parentM || null;
this.className = this.pfx + 'btn' + (cls ? ' ' + cls : '');
@ -21,6 +22,7 @@ module.exports = Backbone.View.extend({
this.listenTo(this.model, 'change:bntsVis', this.updateBtnsVis);
this.listenTo(this.model, 'change:attributes', this.updateAttributes);
this.listenTo(this.model, 'change:className', this.updateClassName);
this.listenTo(this.model, 'change:disable', this.updateDisable);
if(this.model.get('buttons').length){
this.$el.on('mousedown', this.startTimer);
@ -234,6 +236,15 @@ module.exports = Backbone.View.extend({
}
},
updateDisable() {
if(this.model.get('disable')) {
this.$el.addClass(this.disableCls);
} else {
this.$el.removeClass(this.disableCls);
}
},
/**
* Update active style status
*
@ -255,9 +266,18 @@ module.exports = Backbone.View.extend({
clicked(e) {
if(this.model.get('bntsVis') )
return;
if(this.model.get('disable') )
return;
this.toogleActive();
},
toogleActive() {
if(this.parentM)
this.swapParent();
var active = this.model.get('active');
this.model.set('active', !active);

17
src/parser/model/ParserHtml.js

@ -107,13 +107,24 @@ module.exports = config => {
model.classes = this.parseClass(nodeValue);
else if (nodeName == 'contenteditable')
continue;
else if(nodeName.indexOf(modelAttrStart) === 0){
var modelAttr = nodeName.replace(modelAttrStart, '');
else if (nodeName.indexOf(modelAttrStart) === 0) {
const modelAttr = nodeName.replace(modelAttrStart, '');
const valueLen = nodeValue.length;
const firstChar = nodeValue && nodeValue.substr(0, 1);
const lastChar = nodeValue && nodeValue.substr(valueLen - 1);
nodeValue = nodeValue === 'true' ? true : nodeValue;
nodeValue = nodeValue === 'false' ? false : nodeValue;
// Try to json parse where is possible
// I can get false positive here (eg. a selector '[data-attr]')
// so put it under try/catch and let fail silently
try {
nodeValue = (firstChar == '{' && lastChar == '}') ||
(firstChar == '[' && lastChar == ']') ? JSON.parse(nodeValue) : nodeValue;
} catch (e) {}
model[modelAttr] = nodeValue;
}else
} else {
model.attributes[nodeName] = nodeValue;
}
}

14
src/rich_text_editor/index.js

@ -21,6 +21,14 @@ module.exports = () => {
const defaults = require('./config/config');
let toolbar, actions, lastEl, globalRte;
const hideToolbar = () => {
const style = toolbar.style;
const size = '-100px';
style.top = size;
style.left = size;
style.display = 'none';
};
return {
customRte: null,
@ -72,6 +80,7 @@ module.exports = () => {
postRender(ev) {
const canvas = ev.model.get('Canvas');
toolbar.style.pointerEvents = 'all';
hideToolbar();
canvas.getToolsEl().appendChild(toolbar);
},
@ -266,7 +275,6 @@ module.exports = () => {
disable(view, rte) {
const em = config.em;
const customRte = this.customRte;
const style = toolbar.style;
var el = view.getChildrenContainer();
if (customRte) {
@ -275,9 +283,7 @@ module.exports = () => {
rte.disable();
}
style.display = 'none';
style.top = 0;
style.left = 0;
hideToolbar();
em && em.trigger('rte:disable', view, rte);
},
};

29
src/selector_manager/index.js

@ -1,8 +1,4 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
*
* Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To get better this concept let's take
* a look at this code:
*
@ -52,6 +48,9 @@
* statesLabel: '- Selecte State -',
* }
*/
import { isString } from 'underscore'
module.exports = config => {
var c = config || {},
defaults = require('./config/config'),
@ -148,6 +147,28 @@ module.exports = config => {
return selector;
},
/**
* Add class selectors
* @param {Array|string} classes Array or string of classes
* @return {Array} Array of added selectors
* @example
* sm.addClass('class1');
* sm.addClass('class1 class2');
* sm.addClass(['class1', 'class2']);
* // -> [SelectorObject, ...]
*/
addClass(classes) {
const added = [];
if (isString(classes)) {
classes = classes.trim().split(' ');
}
classes.forEach(name => added.push(selectors.add({name})))
return added;
},
/**
* Get the selector by its name
* @param {String} name Selector name

28
src/storage_manager/model/RemoteStorage.js

@ -74,7 +74,7 @@ module.exports = require('backbone').Model.extend({
},
load(keys, clb) {
this.request(this.get('urlLoad'), {body: {keys}}, clb);
this.request(this.get('urlLoad'), {method: 'get'}, clb);
},
/**
@ -91,6 +91,7 @@ module.exports = require('backbone').Model.extend({
const reqHead = 'X-Requested-With';
const typeHead = 'Content-Type';
const bodyObj = opts.body || {};
let fetchOptions;
let body;
for (let param in params) {
@ -117,19 +118,24 @@ module.exports = require('backbone').Model.extend({
body.append(bodyKey, bodyObj[bodyKey]);
}
}
this.onStart();
this.fetch(url, {
fetchOptions = {
method: opts.method || 'post',
credentials: 'include',
headers,
body,
}).then(res => (res.status/200|0) == 1 ?
res.text() : res.text().then((text) =>
Promise.reject(text)
))
.then((text) => this.onResponse(text, clb))
.catch(err => this.onError(err));
};
// Body should only be included on POST method
if (fetchOptions.method === 'post') {
fetchOptions.body = body;
}
this.onStart();
this.fetch(url, fetchOptions)
.then(res => (res.status/200|0) == 1
? res.text()
: res.text().then((text) => Promise.reject(text)))
.then((text) => this.onResponse(text, clb))
.catch(err => this.onError(err));
},
});

9
src/style_manager/config/config.js

@ -23,11 +23,6 @@ module.exports = {
// Adds the possibility to clear property value from the target style
clearProperties: false,
// Properties which are valid to be shown as computed
// (Identified as inherited properties: https://developer.mozilla.org/en-US/docs/Web/CSS/inheritance)
validComputed: ['border-collapse', 'border-spacing', 'caption-side', 'color', 'cursor', 'direction', 'empty-cells',
'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight', 'font-size-adjust', 'font-stretch', 'font',
'letter-spacing', 'line-height', 'list-style-image', 'list-style-position', 'list-style-type', 'list-style', 'orphans',
'quotes', 'tab-size', 'text-align', 'text-align-last', 'text-decoration-color', 'text-indent', 'text-justify',
'text-shadow', 'text-transform', 'visibility', 'white-space', 'widows', 'word-break', 'word-spacing', 'word-wrap'],
// Properties not to take in account for computed styles
avoidComputed: ['width', 'height'],
};

44
src/style_manager/index.js

@ -10,6 +10,7 @@
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
* * [createType](#createtype)
* * [render](#render)
*
* With Style Manager you basically build categories (called sectors) of CSS properties which could
@ -69,6 +70,7 @@ module.exports = () => {
*/
name: 'StyleManager',
/**
* Get configuration object
* @return {Object}
@ -78,6 +80,7 @@ module.exports = () => {
return c;
},
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
@ -94,7 +97,7 @@ module.exports = () => {
c.stylePrefix = ppfx + c.stylePrefix;
properties = new Properties();
sectors = new Sectors(c.sectors);
sectors = new Sectors(c.sectors, c);
SectView = new SectorsView({
collection: sectors,
target: c.em,
@ -103,6 +106,7 @@ module.exports = () => {
return this;
},
/**
* Add new sector to the collection. If the sector with the same id already exists,
* that one will be returned
@ -128,6 +132,7 @@ module.exports = () => {
return result;
},
/**
* Get sector by id
* @param {string} id Sector id
@ -140,6 +145,7 @@ module.exports = () => {
return res.length ? res[0] : null;
},
/**
* Get all sectors
* @return {Sectors} Collection of sectors
@ -148,6 +154,7 @@ module.exports = () => {
return sectors;
},
/**
* Add property to the sector identified by id
* @param {string} sectorId Sector id
@ -193,6 +200,7 @@ module.exports = () => {
return prop;
},
/**
* Get property by its CSS name and sector id
* @param {string} sectorId Sector id
@ -213,6 +221,7 @@ module.exports = () => {
return prop;
},
/**
* Get properties of the sector
* @param {string} sectorId Sector id
@ -230,6 +239,7 @@ module.exports = () => {
return props;
},
/**
* Get what to style inside Style Manager. If you select the component
* without classes the entity is the Component itself and all changes will
@ -258,6 +268,7 @@ module.exports = () => {
return model;
},
/**
* Add new property type
* @param {string} id Type ID
@ -279,6 +290,7 @@ module.exports = () => {
properties.addType(id, definition);
},
/**
* Get type
* @param {string} id Type ID
@ -288,6 +300,7 @@ module.exports = () => {
return properties.getType(id);
},
/**
* Get all types
* @return {Array}
@ -296,6 +309,35 @@ module.exports = () => {
return properties.getTypes();
},
/**
* Create new property from type
* @param {string} id Type ID
* @param {Object} [options={}] Options
* @param {Object} [options.model={}] Custom model object
* @param {Object} [options.view={}] Custom view object
* @return {PropertyView}
* @example
* const propView = styleManager.createType('integer', {
* model: {units: ['px', 'rem']}
* });
* propView.render();
* propView.model.on('change:value', ...);
* someContainer.appendChild(propView.el);
*/
createType(id, {model = {}, view = {}} = {}) {
const type = this.getType(id);
if (type) {
return new type.view({
model: new type.model(model),
config: c,
...view
});
}
},
/**
* Render sectors and properties
* @return {HTMLElement}

2
src/style_manager/model/Layers.js

@ -64,7 +64,7 @@ module.exports = Backbone.Collection.extend({
const style = styleObj[propModel.get('property')];
const values = style ? style.split(', ') : [];
values.forEach((value, i) => {
value = propModel.parseValue(value.trim());
value = propModel.parseValue(value.trim()).value;
const layer = layers[i];
const propertyObj = Object.assign({}, propModel.attributes, {value});

54
src/style_manager/model/Property.js

@ -14,6 +14,7 @@ module.exports = require('backbone').Model.extend({
fixedValues: ['initial', 'inherit'],
},
initialize(opt) {
var o = opt || {};
var name = this.get('name');
@ -27,18 +28,58 @@ module.exports = require('backbone').Model.extend({
init && init();
},
/**
* 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);
this.set(parsed, { ...opts, avoidStore: 1});
// It's important to set an empty value, otherwise the
// UndoManager won't see the change
if (complete) {
this.set('value', '', opts);
this.set(parsed, 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
* @return {string}
* @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) {
const result = { value };
if (!this.get('functionName')) {
return value;
return result;
}
const args = [];
let valueStr = value + '';
let valueStr = `${value}`;
let start = valueStr.indexOf('(') + 1;
let end = valueStr.lastIndexOf(')');
args.push(start);
@ -48,9 +89,11 @@ module.exports = require('backbone').Model.extend({
args.push(end);
}
return String.prototype.substring.apply(valueStr, args);
result.value = String.prototype.substring.apply(valueStr, args);
return result;
},
/**
* Get the default value
* @return {string}
@ -60,6 +103,7 @@ module.exports = require('backbone').Model.extend({
return this.get('defaults');
},
/**
* Get a complete value of the property.
* This probably will replace the getValue when all

25
src/style_manager/model/PropertyComposite.js

@ -16,14 +16,37 @@ module.exports = Property.extend({
// Array of sub properties
properties: [],
// Separator between properties
separator: ' ',
}),
init() {
const properties = this.get('properties') || [];
const Properties = require('./Properties');
this.set('properties', new Properties(properties));
this.listenTo(this, 'change:value', this.updateValues)
},
/**
* Update property values
*/
updateValues() {
const values = this.get('value').split(this.get('separator'));
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});
});
},
/**
* Returns default value
* @param {Boolean} defaultProps Force to get defaults from properties
@ -42,12 +65,12 @@ module.exports = Property.extend({
return value.trim();
},
getFullValue() {
if (this.get('detached')) {
return '';
}
let result = '';
return this.get('properties').getFullValue();
},

24
src/style_manager/model/PropertyFactory.js

@ -209,7 +209,8 @@ module.exports = () => ({
}
// Min/Max
switch(prop){
switch(prop) {
case 'padding-top': case 'padding-right': case 'padding-bottom': case 'padding-left':
case 'min-height': case 'min-width': case 'max-height': case 'max-width':
case 'width': case 'height':
case 'font-size':
@ -290,17 +291,26 @@ module.exports = () => ({
break;
case 'font-family':
var ss = ', sans-serif';
var s = ', serif';
var fonts = ['Arial, Helvetica' + ss, 'Arial Black, Gadget' + ss, 'Brush Script MT' + ss,
'Comic Sans MS, cursive' + ss, 'Courier New, Courier, monospace', 'Georgia, serif', 'Helvetica, serif',
'Impact, Charcoal' + ss, 'Lucida Sans Unicode, Lucida Grande' + ss, 'Tahoma, Geneva' + ss,
'Times New Roman, Times, serif', 'Trebuchet MS, Helvetica' + ss, 'Verdana, Geneva' + ss];
var fonts = [
'Arial, Helvetica' + ss,
'Arial Black, Gadget' + ss,
'Brush Script MT' + ss,
'Comic Sans MS, cursive' + ss,
'Courier New, Courier, monospace',
'Georgia, serif',
'Helvetica, serif',
'Impact, Charcoal' + ss,
'Lucida Sans Unicode, Lucida Grande' + ss,
'Tahoma, Geneva' + ss,
'Times New Roman, Times, serif',
'Trebuchet MS, Helvetica' + ss,
'Verdana, Geneva' + ss
];
obj.list = [];
for(var j = 0, l = fonts.length; j < l; j++){
var font = {};
font.value = fonts[j];
font.name = fonts[j].split(',')[0];
font.style = 'font-family: ' + fonts[j] + '; font-size:15px';
obj.list.push(font);
}
break;

17
src/style_manager/model/PropertyInteger.js

@ -1,8 +1,9 @@
const Property = require('./Property');
const InputNumber = require('domain_abstract/ui/InputNumber');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
defaults: { ...Property.prototype.defaults,
// Array of units, eg. ['px', '%']
units: [],
@ -17,17 +18,29 @@ module.exports = Property.extend({
// Maximum value
max: '',
}),
},
init() {
const unit = this.get('unit');
const units = this.get('units');
this.input = new InputNumber({ model: this });
if (units.length && !unit) {
this.set('unit', units[0]);
}
},
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') + this.get('unit');
return Property.prototype.getFullValue.apply(this, [value]);

6
src/style_manager/model/PropertySlider.js

@ -2,8 +2,8 @@ const Property = require('./PropertyInteger');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
showInput: 1,
}),
defaults: { ...Property.prototype.defaults,
showInput: 1,
},
});

4
src/style_manager/model/Sector.js

@ -18,6 +18,10 @@ module.exports = Backbone.Model.extend({
var props = [];
var builded = this.buildProperties(o.buildProps);
if (!this.get('id')) {
this.set('id', this.get('name'))
}
if(!builded)
props = this.get('properties');
else

2
src/style_manager/model/Sectors.js

@ -1,5 +1,5 @@
const Sector = require('./Sector');
module.exports = require('backbone').Collection.extend({
model: Sector,
model: Sector
});

60
src/style_manager/view/PropertiesView.js

@ -17,33 +17,49 @@ module.exports = Backbone.View.extend({
this.onChange = o.onChange;
this.onInputRender = o.onInputRender || {};
this.customValue = o.customValue || {};
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.render);
},
render() {
var fragment = document.createDocumentFragment();
this.collection.each((model) => {
var view = new model.typeView({
model,
name: model.get('name'),
id: this.pfx + model.get('property'),
target: this.target,
propTarget: this.propTarget,
onChange: this.onChange,
onInputRender: this.onInputRender,
config: this.config,
});
if(model.get('type') != 'composite'){
view.customValue = this.customValue;
}
view.render();
fragment.appendChild(view.el);
addTo(model) {
this.add(model);
},
add(model, frag) {
var view = new model.typeView({
model,
name: model.get('name'),
id: this.pfx + model.get('property'),
target: this.target,
propTarget: this.propTarget,
onChange: this.onChange,
onInputRender: this.onInputRender,
config: this.config,
});
if (model.get('type') != 'composite') {
view.customValue = this.customValue;
}
view.render();
const el = view.el;
if (frag) {
frag.appendChild(el);
} else {
this.el.appendChild(el);
}
},
render() {
const fragment = document.createDocumentFragment();
this.collection.each(model => this.add(model, fragment));
this.$el.append(fragment);
this.$el.attr('class', this.pfx + 'properties');
this.$el.attr('class', `${this.pfx}properties`);
return this;
}
});

7
src/style_manager/view/PropertyCompositeView.js

@ -13,8 +13,11 @@ module.exports = PropertyView.extend({
},
inputValueChanged(...args) {
if(!this.model.get('detached'))
// If it's not detached (eg. 'padding: 1px 2px 3px 4px;') it will follow
// the same flow of PropertyView
if (!this.model.get('detached')) {
PropertyView.prototype.inputValueChanged.apply(this, args);
}
},
/**
@ -107,7 +110,7 @@ module.exports = PropertyView.extend({
}
if (view) {
value = view.model.parseValue(value);
value = view.model.parseValue(value).value;
}
return value;

8
src/style_manager/view/PropertyIntegerView.js

@ -21,11 +21,9 @@ module.exports = require('./PropertyView').extend({
const ppfx = this.ppfx;
if (!this.input) {
const inputNumber = new InputNumber({
model: this.model,
ppfx: this.ppfx
});
const input = inputNumber.render();
const input = this.model.input;
input.ppfx = ppfx;
input.render();
const fields = this.el.querySelector(`.${ppfx}fields`);
fields.appendChild(input.el);
this.$input = input.inputEl;

5
src/style_manager/view/PropertyRadioView.js

@ -5,7 +5,6 @@ module.exports = require('./PropertyView').extend({
const ppfx = this.ppfx;
return `
<div class="${ppfx}field ${ppfx}field-radio">
<span id="${pfx}input-holder"></span>
</div>
`;
},
@ -35,8 +34,8 @@ module.exports = require('./PropertyView').extend({
`;
});
const inputHld = this.el.querySelector(`#${pfx}input-holder`);
inputHld.innerHTML = `<div>${inputStr}</div>`;
const inputHld = this.el.querySelector(`.${ppfx}field`);
inputHld.innerHTML = `<div class="${ppfx}radio-items">${inputStr}</div>`;
this.input = inputHld.firstChild;
}
}

14
src/style_manager/view/PropertySliderView.js

@ -1,13 +1,13 @@
const InputNumber = require('domain_abstract/ui/InputNumber');
const Property = require('./PropertyIntegerView');
module.exports = Property.extend({
events: {
'change': 'inputValueChanged',
'input': 'inputValueChangedSoft',
'change [type=range]': 'inputValueChanged',
'input [type=range]': 'inputValueChangedSoft',
},
templateInput(model) {
const ppfx = this.ppfx;
return `
@ -20,6 +20,7 @@ module.exports = Property.extend({
`;
},
getSliderEl() {
if (!this.slider) {
this.slider = this.el.querySelector('input[type=range]');
@ -28,6 +29,7 @@ module.exports = Property.extend({
return this.slider;
},
inputValueChanged() {
const model = this.model;
const step = model.get('step');
@ -37,22 +39,24 @@ module.exports = Property.extend({
this.elementUpdated();
},
inputValueChangedSoft() {
this.getInputEl().value = this.getSliderEl().value;
this.model.set('value', this.getInputValue(), {avoidStore: 1});
this.elementUpdated();
},
setValue(value) {
this.getSliderEl().value = value;
this.inputInst.setValue(value, {silent: 1});
},
onRender() {
Property.prototype.onRender.apply(this, arguments);
const model = this.model;
if (!model.get('showInput')) {
if (!this.model.get('showInput')) {
this.inputInst.el.style.display = 'none';
}
}

80
src/style_manager/view/PropertyView.js

@ -1,3 +1,6 @@
import { bindAll } from 'underscore';
import { camelCase } from 'utils/mixins';
module.exports = Backbone.View.extend({
template(model) {
@ -37,8 +40,10 @@ module.exports = Backbone.View.extend({
},
initialize(o = {}) {
bindAll(this, 'targetUpdated');
this.config = o.config || {};
this.em = this.config.em;
const em = this.config.em;
this.em = em;
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.target = o.target || {};
@ -52,11 +57,13 @@ module.exports = Backbone.View.extend({
const pfx = this.pfx;
this.inputHolderId = '#' + pfx + 'input-holder';
this.sector = model.collection && model.collection.sector;
model.view = this;
if (!model.get('value')) {
model.set('value', model.getDefaultValue());
}
em && em.on(`update:component:style:${this.property}`, this.targetUpdated);
this.listenTo(this.propTarget, 'update', this.targetUpdated);
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:value', this.modelValueChanged);
@ -146,8 +153,9 @@ module.exports = Backbone.View.extend({
* Triggers when the value of element input/s is changed, so have to update
* the value of the model which will propogate those changes to the target
*/
inputValueChanged() {
this.model.set('value', this.getInputValue());
inputValueChanged(e) {
e && e.stopPropagation();
this.model.setValue(this.getInputValue(), 1, { fromInput: 1 });
this.elementUpdated();
},
@ -156,8 +164,19 @@ module.exports = Backbone.View.extend({
*/
elementUpdated() {
this.model.set('status', 'updated');
const parent = this.model.parent;
const parentView = parent && parent.view;
parentView && parentView.elementUpdated();
},
setStatus(value) {
this.model.set('status', value);
const parent = this.model.parent;
parent && parent.set('status', value);
},
/**
* Fired when the target is changed
* */
@ -193,9 +212,8 @@ module.exports = Backbone.View.extend({
status = '';
}
model.set('value', value, {silent: 1});
this.setValue(value, {targetUpdate: 1});
model.set('status', status);
model.setValue(value, 0, { fromTarget: 1 });
this.setStatus(status);
if (em) {
em.trigger('styleManager:change', this);
@ -243,11 +261,6 @@ module.exports = Backbone.View.extend({
result = target.getStyle()[model.get('property')];
// TODO when stack type asks the sub-property (in valueOnIndex method)
// to provide its target value and its detached, I should avoid parsing
// (at least is wrong applying 'functionName' cleaning)
result = model.parseValue(result);
if (!result && !opts.ignoreDefault) {
result = model.getDefaultValue();
}
@ -270,10 +283,15 @@ module.exports = Backbone.View.extend({
* @private
*/
getComputedValue() {
let computed = this.propTarget.computed;
const valid = this.config.validComputed;
const target = this.propTarget;
const computed = target.computed || {};
const computedDef = target.computedDefault || {};
const avoid = this.config.avoidComputed || [];
const property = this.model.get('property');
return computed && valid.indexOf(property) >= 0 && computed[property];
const notToSkip = avoid.indexOf(property) < 0;
const value = computed[property];
const valueDef = computedDef[camelCase(property)];
return computed && notToSkip && valueDef !== value && value;
},
/**
@ -292,23 +310,32 @@ module.exports = Backbone.View.extend({
* @param {Mixed} val Value
* @param {Object} opt Options
* */
modelValueChanged(e, val, opt) {
modelValueChanged(e, val, opt = {}) {
const em = this.config.em;
const model = this.model;
const value = model.getFullValue();
const target = this.getTarget();
const onChange = this.onChange;
this.setRawValue(value);
// Avoid element update if the change comes from it
if (!opt.fromInput) {
this.setValue(value);
}
// Check if component is allowed to be styled
if (!target || !this.isTargetStylable() || !this.isComponentStylable()) {
return;
}
if (onChange) {
onChange(target, this, opt);
} else {
this.updateTargetStyle(value, null, opt);
// Avoid target update if the changes comes from it
if (!opt.fromTarget) {
// The onChange is used by Composite/Stack properties, so I'd avoid sending
// it back if the change comes from one of those
if (onChange && !opt.fromParent) {
onChange(target, this, opt);
} else {
this.updateTargetStyle(value, null, opt);
}
}
if (em) {
@ -391,19 +418,20 @@ module.exports = Backbone.View.extend({
this.setValue(this.model.parseValue(value));
},
/**
* Set the value to property input
* @param {String} value
* @param {Boolean} force
* @private
* Update the element input.
* Usually the value is a result of `model.getFullValue()`
* @param {String} value The value from the model
* */
setValue(value, opts = {}) {
setValue(value) {
const model = this.model;
let val = value || model.get('value') || model.getDefaultValue();
let val = value || model.getDefaultValue();
const input = this.getInputEl();
input && (input.value = val);
},
getInputEl() {
if (!this.input) {
this.input = this.el.querySelector('input');

13
src/style_manager/view/SectorsView.js

@ -1,3 +1,5 @@
import { extend } from 'underscore';
const SectorView = require('./SectorView');
module.exports = Backbone.View.extend({
@ -8,8 +10,15 @@ module.exports = Backbone.View.extend({
this.target = o.target || {};
// The taget that will emit events for properties
this.propTarget = {};
_.extend(this.propTarget, Backbone.Events);
const target = {};
extend(target, Backbone.Events);
const body = document.body;
const dummy = document.createElement(`el-${new Date().getTime()}`);
body.appendChild(dummy);
target.computedDefault = { ...window.getComputedStyle(dummy) };
body.removeChild(dummy);
this.propTarget = target;
this.listenTo( this.collection, 'add', this.addTo);
this.listenTo( this.collection, 'reset', this.render);
this.listenTo( this.target, 'change:selectedComponent targetClassAdded targetClassRemoved targetClassUpdated ' +

1
src/styles/scss/_gjs_canvas.scss

@ -34,6 +34,7 @@
.#{$app-prefix}highlighter-sel {
position: absolute;
outline: 1px solid $colorBlue;
outline-offset: -1px;
pointer-events: none;
}

78
src/styles/scss/_gjs_inputs.scss

@ -49,9 +49,34 @@
}
&field {
input,
select,
textarea {
@include appearance(none);
color: inherit;
border: none;
background-color: transparent;
box-sizing: border-box;
width: 100%;
position: relative;
padding: $inputPadding;
z-index: 1;
&:focus {
outline: none;
}
}
&-range {
flex: 9 1 auto;
}
&-integer {
input {
padding-right: 30px;
}
}
}
}
@ -59,7 +84,8 @@
.#{$clm-prefix}select option,
.#{$sm-prefix}select option,
.#{$sm-prefix}unit option {
@extend .#{$app-prefix}bg-main;
background-color: $mainColor;
color: $fontColor;
}
.#{$app-prefix}field {
@ -72,33 +98,10 @@
position: relative;
color: $inputFontColor;
input,
select,
textarea {
@include appearance(none);
color: inherit;
border: none;
background-color: transparent;
box-sizing: border-box;
width: 100%;
position: relative;
padding: $inputPadding;
z-index: 1;
&:focus {
outline: none;
}
}
textarea {
resize: vertical;
}
option {
padding: 3px 0;
}
.#{$app-prefix}sel-arrow {
height: 100%;
width: 9px;
@ -122,10 +125,6 @@
cursor: pointer;
}
.#{$app-prefix}field-number {
padding-right: 30px;
}
&-arrows {
position: absolute;
cursor: ns-resize;
@ -137,6 +136,11 @@
right: $inputPadding - 2px;
top: 0;
}
&-color,
&-radio {
width: 100%;
}
}
.#{$app-prefix}field-color {
@ -183,13 +187,6 @@
top: 0;
}
.#{$app-prefix}field-radio {
float: left;
overflow: hidden;
padding: 0;
width: auto;
}
/* ??? */
.#{$app-prefix}field-checkbox {
padding: 0;
@ -207,7 +204,8 @@
}
.#{$app-prefix}radio-item {
float: left;
flex: 1 1 auto;
text-align: center;
border-left: 1px solid $darkTextShadow;
&:first-child {
@ -225,6 +223,10 @@
input:checked + .#{$app-prefix}radio-item-label {
background-color: rgba(255, 255, 255, 0.2);
}
&s {
display: flex;
}
}
.#{$app-prefix}radio-item-label {
@ -270,10 +272,6 @@
.#{$app-prefix}field-select {
padding: 0;
option {
@extend .#{$app-prefix}bg-main;
}
}
.#{$app-prefix}field-range {

4
src/styles/scss/main.scss

@ -444,10 +444,10 @@
/********* Device Manager **********/
.#{$app-prefix}devices-c{
.#{$app-prefix}devices-c {
display: flex;
align-items: center;
padding: 4px;
padding: 3px;
/*
padding: 10px 5px;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);

15
src/trait_manager/config/config.js

@ -1,4 +1,19 @@
module.exports = {
stylePrefix: 'trt-',
labelContainer: 'Component settings',
// Placeholder label for text input types
labelPlhText: 'eg. Text here',
// Placeholder label for href input
labelPlhHref: 'eg. https://google.com',
// Default options for the target input
optionsTarget: [
{ value: '', name: 'This window' },
{ value: '_blank', name: 'New window' }
]
};

26
src/trait_manager/index.js

@ -1,9 +1,10 @@
import { defaults } from 'underscore';
module.exports = () => {
var c = {},
defaults = require('./config/config'),
Traits = require('./model/Traits'),
TraitsView = require('./view/TraitsView');
var TraitsViewer;
let c = {};
const defaultOpts = require('./config/config');
const TraitsView = require('./view/TraitsView');
let TraitsViewer;
return {
@ -29,16 +30,11 @@ module.exports = () => {
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
init(config = {}) {
c = config;
defaults(c, defaultOpts);
const ppfx = c.pStylePrefix;
ppfx && (c.stylePrefix = `${ppfx}${c.stylePrefix}`);
TraitsViewer = new TraitsView({
collection: [],
editor: c.em,

15
src/trait_manager/model/TraitFactory.js

@ -1,6 +1,4 @@
var Backbone = require('backbone');
module.exports = (() => ({
module.exports = (config = {}) => ({
/**
* Build props object by their name
* @param {Array<string>|string} props Array of properties name
@ -27,10 +25,10 @@ module.exports = (() => ({
// Define placeholder
switch (prop) {
case 'title': case 'alt': case 'id':
obj.placeholder = 'eg. Text here';
obj.placeholder = config.labelPlhText;
break;
case 'href':
obj.placeholder = 'eg. https://google.com';
obj.placeholder = config.labelPlhHref;
break;
}
@ -38,10 +36,7 @@ module.exports = (() => ({
// Define options
switch (prop) {
case 'target':
obj.options = [
{value: '', name: 'This window'},
{value: '_blank', name: 'New window'}
];
obj.options = config.optionsTarget;
break;
}
@ -50,4 +45,4 @@ module.exports = (() => ({
return objs;
}
}))();
});

35
src/trait_manager/model/Traits.js

@ -1,28 +1,41 @@
var Backbone = require('backbone');
var Trait = require('./Trait');
var TraitFactory = require('./TraitFactory');
import { isString, isArray } from 'underscore';
const Backbone = require('backbone');
const Trait = require('./Trait');
const TraitFactory = require('./TraitFactory');
module.exports = Backbone.Collection.extend({
model: Trait,
initialize(coll, options = {}) {
this.em = options.em || '';
},
setTarget(target) {
this.target = target;
},
add(models, opt) {
const em = this.em;
// Use TraitFactory if necessary
if(typeof models === 'string' || models instanceof Array) {
if(typeof models === 'string')
models = [models];
for(var i = 0, len = models.length; i < len; i++) {
var str = models[i];
var model = typeof str === 'string' ? TraitFactory.build(str)[0] : str;
if (isString(models) || isArray(models)) {
const tm = em && em.get && em.get('TraitManager');
const tmOpts = tm && tm.getConfig();
const tf = TraitFactory(tmOpts);
if (isString(models)) {
models = [models];
}
for (var i = 0, len = models.length; i < len; i++) {
const str = models[i];
const model = isString(str) ? tf.build(str)[0] : str;
model.target = this.target;
models[i] = model;
}
}
return Backbone.Collection.prototype.add.apply(this, [models, opt]);
},
return Backbone.Collection.prototype.add.apply(this, [models, opt]);
}
});

175
src/utils/Resizer.js

@ -1,6 +1,7 @@
const $ = Backbone.$;
import { bindAll, defaults, isFunction } from 'underscore';
import { on, off } from 'utils/mixins';
var defaults = {
var defaultOpts = {
// Function which returns custom X and Y coordinates of the mouse
mousePosFetcher: null,
// Indicates custom target updating strategy
@ -11,6 +12,26 @@ var defaults = {
onStart: null,
onMove: null,
onEnd: null,
// Minimum dimension
minDim: 32,
// Unit used for height resizing
unitHeight: 'px',
// Unit used for width resizing
unitWidth: 'px',
// The key used for height resizing
keyHeight: 'height',
// The key used for width resizing
keyWidth: 'width',
// If true, will override unitHeight and unitWidth, on start, with units
// from the current focused element (currently used only in SelectComponent)
currentUnit: 1,
// Handlers
tl: 1, // Top left
tc: 1, // Top center
@ -49,21 +70,26 @@ class Resizer {
*/
constructor(opts = {}) {
this.setOptions(opts);
bindAll(this, 'handleKeyDown', 'handleMouseDown', 'move', 'stop')
return this;
}
/**
* Get current connfiguration options
* @return {Object}
*/
getConfig() {
return this.opts;
}
/**
* Setup options
* @param {Object} options
*/
setOptions(options = {}) {
// Setup default options
for (var name in defaults) {
if (!(name in options))
options[name] = defaults[name];
}
this.opts = options;
this.opts = defaults(options, defaultOpts);
this.setup();
}
@ -74,45 +100,31 @@ class Resizer {
const opts = this.opts;
const pfx = opts.prefix || '';
const appendTo = opts.appendTo || document.body;
let container;
let container = this.container;
// Create container if not yet exist
if (!this.container) {
if (!container) {
container = document.createElement('div');
container.className = pfx + 'resizer-c';
container.className = `${pfx}resizer-c`;
appendTo.appendChild(container);
this.container = container;
}
container = this.container;
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Create handlers
var handlers = {
tl: opts.tl ? createHandler('tl', opts) : '',
tc: opts.tc ? createHandler('tc', opts) : '',
tr: opts.tr ? createHandler('tr', opts) : '',
cl: opts.cl ? createHandler('cl', opts) : '',
cr: opts.cr ? createHandler('cr', opts) : '',
bl: opts.bl ? createHandler('bl', opts) : '',
bc: opts.bc ? createHandler('bc', opts) : '',
br: opts.br ? createHandler('br', opts) : '',
};
const handlers = {};
['tl', 'tc', 'tr', 'cl', 'cr', 'bl', 'bc', 'br'].forEach(hdl =>
handlers[hdl] = opts[hdl] ? createHandler(hdl, opts) : '');
for (let n in handlers) {
const handler = handlers[n];
if (handler) {
container.appendChild(handler);
}
handler && container.appendChild(handler);
}
this.handlers = handlers;
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.move = this.move.bind(this);
this.stop = this.stop.bind(this);
this.mousePosFetcher = opts.mousePosFetcher;
this.updateTarget = opts.updateTarget;
this.posFetcher = opts.posFetcher;
@ -148,10 +160,7 @@ class Resizer {
* Returns documents
*/
getDocumentEl() {
if (!this.$doc) {
this.$doc = $([this.el.ownerDocument, document]);
}
return this.$doc;
return [this.el.ownerDocument, document];
}
/**
@ -174,18 +183,19 @@ class Resizer {
return;
}
// Show the handlers
this.el = el;
var unit = 'px';
var rect = this.getElementPos(el);
var container = this.container;
var contStyle = container.style;
var unit = 'px';
contStyle.left = rect.left + unit;
contStyle.top = rect.top + unit;
contStyle.width = rect.width + unit;
contStyle.height = rect.height + unit;
this.container.style.display = 'block';
container.style.display = 'block';
this.getDocumentEl().on('mousedown', this.handleMouseDown);
on(this.getDocumentEl(), 'mousedown', this.handleMouseDown);
}
/**
@ -193,9 +203,9 @@ class Resizer {
*/
blur() {
this.container.style.display = 'none';
if(this.el) {
var doc = $([this.el.ownerDocument, document]);
this.getDocumentEl().off('mousedown', this.handleMouseDown);
if (this.el) {
off(this.getDocumentEl(), 'mousedown', this.handleMouseDown);
this.el = null;
}
}
@ -211,9 +221,11 @@ class Resizer {
}
e.preventDefault();
e.stopPropagation();
var opts = this.opts || {};
var attrName = 'data-' + opts.prefix + 'handler';
var rect = this.getElementPos(this.el);
const el = this.el;
const resizer = this;
const config = this.opts || {};
var attrName = 'data-' + config.prefix + 'handler';
var rect = this.getElementPos(el);
this.handlerAttr = e.target.getAttribute(attrName);
this.clickedHandler = e.target;
this.startDim = {
@ -235,15 +247,11 @@ class Resizer {
// Listen events
var doc = this.getDocumentEl();
doc.on('mousemove', this.move);
doc.on('keydown', this.handleKeyDown);
doc.on('mouseup', this.stop);
on(doc, 'mousemove', this.move);
on(doc, 'keydown', this.handleKeyDown);
on(doc, 'mouseup', this.stop);
isFunction(this.onStart) && this.onStart(e, {docs: doc, config, el, resizer});
this.move(e);
// Start callback
if(typeof this.onStart === 'function') {
this.onStart(e, {docs: doc});
}
}
/**
@ -251,6 +259,7 @@ class Resizer {
* @param {Event} e
*/
move(e) {
const onMove = this.onMove;
var mouseFetch = this.mousePosFetcher;
var currentPos = mouseFetch ? mouseFetch(e) : {
x: e.clientX,
@ -267,15 +276,12 @@ class Resizer {
ctrl: e.ctrlKey,
alt: e.altKey
};
//console.log('move resizer ', this.currentPos);
this.rectDim = this.calc(this);
this.updateRect(0);
// Move callback
if(typeof this.onMove === 'function') {
this.onMove(e);
}
onMove && onMove(e);
// In case the mouse button was released outside of the window
if (e.which === 0) {
@ -288,46 +294,48 @@ class Resizer {
* @param {Event} e
*/
stop(e) {
const config = this.opts;
var doc = this.getDocumentEl();
doc.off('mousemove', this.move);
doc.off('keydown', this.handleKeyDown);
doc.off('mouseup', this.stop);
off(doc, 'mousemove', this.move);
off(doc, 'keydown', this.handleKeyDown);
off(doc, 'mouseup', this.stop);
this.updateRect(1);
// Stop callback
if (typeof this.onEnd === 'function') {
this.onEnd(e, {docs: doc});
}
isFunction(this.onEnd) && this.onEnd(e, {docs: doc, config});
}
/**
* Update rect
*/
updateRect(store) {
var elStyle = this.el.style;
var conStyle = this.container.style;
var rect = this.rectDim;
const el = this.el;
const resizer = this;
const config = this.opts;
const rect = this.rectDim;
const conStyle = this.container.style;
const updateTarget = this.updateTarget;
const selectedHandler = this.getSelectedHandler();
const { unitHeight, unitWidth } = config;
// Use custom updating strategy if requested
if (typeof this.updateTarget === 'function') {
this.updateTarget(this.el, rect, {
if (isFunction(updateTarget)) {
updateTarget(el, rect, {
store,
selectedHandler
selectedHandler,
resizer,
config
});
} else {
elStyle.width = rect.w + 'px';
elStyle.height = rect.h + 'px';
//elStyle.top = rect.top + 'px';
//elStyle.left = rect.left + 'px';
const elStyle = el.style;
elStyle.width = rect.w + unitWidth;
elStyle.height = rect.h + unitHeight;
}
var rectEl = this.getElementPos(this.el);
var unit = 'px';
conStyle.left = rectEl.left + unit;
conStyle.top = rectEl.top + unit;
conStyle.width = rectEl.width + unit;
conStyle.height = rectEl.height + unit;
const unitRect = 'px';
const rectEl = this.getElementPos(el);
conStyle.left = rectEl.left + unitRect;
conStyle.top = rectEl.top + unitRect;
conStyle.width = rectEl.width + unitRect;
conStyle.height = rectEl.height + unitRect;
}
/**
@ -380,6 +388,7 @@ class Resizer {
calc(data) {
var opts = this.opts || {};
var startDim = this.startDim;
var minDim = opts.minDim;
var box = {
t: 0,
l: 0,
@ -392,16 +401,16 @@ class Resizer {
var attr = data.handlerAttr;
if (~attr.indexOf('r')) {
box.w = Math.max(32, startDim.w + data.delta.x);
box.w = Math.max(minDim, startDim.w + data.delta.x);
}
if (~attr.indexOf('b')) {
box.h = Math.max(32, startDim.h + data.delta.y);
box.h = Math.max(minDim, startDim.h + data.delta.y);
}
if (~attr.indexOf('l')) {
box.w = Math.max(32, startDim.w - data.delta.x);
box.w = Math.max(minDim, startDim.w - data.delta.x);
}
if (~attr.indexOf('t')) {
box.h = Math.max(32, startDim.h - data.delta.y);
box.h = Math.max(minDim, startDim.h - data.delta.y);
}
// Enforce aspect ratio (unless shift key is being held)

190
src/utils/Sorter.js

@ -1,3 +1,4 @@
import {on, off} from 'utils/mixins';
const $ = Backbone.$;
module.exports = Backbone.View.extend({
@ -56,6 +57,15 @@ module.exports = Backbone.View.extend({
return this.el;
},
getDocuments() {
const em = this.em;
const canvasDoc = em && em.get('Canvas').getBody().ownerDocument;
const docs = [document];
canvasDoc && docs.push(canvasDoc);
return docs;
},
/**
* Triggered when the offset of the editro is changed
*/
@ -226,39 +236,43 @@ module.exports = Backbone.View.extend({
* @param {HTMLElement} src
* */
startSort(src) {
const em = this.em;
const itemSel = this.itemSel;
const contSel = this.containerSel;
const container = this.getContainerEl();
const docs = this.getDocuments();
const onStart = this.onStart;
let plh = this.plh;
this.dropModel = null;
this.moved = 0;
//this.$document = $([document, trg.ownerDocument]);
if(src && !this.matches(src, this.itemSel + ',' + this.containerSel))
src = this.closest(src, this.itemSel);
// Check if the start element is a valid one, if not get the
// closest valid one
if (src && !this.matches(src, `${itemSel}, ${contSel}`)) {
src = this.closest(src, itemSel);
}
this.eV = src;
// Create placeholder if not exists
if (!this.plh) {
this.plh = this.createPlaceholder();
this.getContainerEl().appendChild(this.plh);
// Create placeholder if not yet exists
if (!plh) {
plh = this.createPlaceholder();
container.appendChild(plh);
this.plh = plh;
}
if (src) {
var srcModel = this.getSourceModel();
const srcModel = this.getSourceModel(src);
srcModel && srcModel.set && srcModel.set('status', 'freezed');
this.$document.on('mouseup', this.endMove);
}
this.$el.on('mousemove', this.onMove);
$(document).on('keydown', this.rollback);
this.$document.on('keydown', this.rollback);
if(typeof this.onStart === 'function')
this.onStart();
on(container, 'mousemove', this.onMove);
on(docs, 'mouseup', this.endMove);
on(docs, 'keydown', this.rollback);
onStart && onStart();
// Avoid strange effects on dragging
if(this.em) {
this.em.clearSelection();
}
em && em.clearSelection();
this.toggleSortCursor(1);
},
@ -275,8 +289,8 @@ module.exports = Backbone.View.extend({
* Get the model of the current source element (element to drag)
* @return {Model}
*/
getSourceModel() {
var src = this.eV;
getSourceModel(source) {
var src = source || this.eV;
let dropContent = this.dropContent;
let dropModel = this.dropModel;
const em = this.em;
@ -322,7 +336,6 @@ module.exports = Backbone.View.extend({
* */
onMove(e) {
this.moved = 1;
// Turn placeholder visibile
var plh = this.plh;
var dsp = plh.style.display;
@ -342,8 +355,12 @@ module.exports = Backbone.View.extend({
rY = mousePos.y;
}
var dims = this.dimsFromTarget(e.target, rX, rY);
this.rX = rX;
this.rY = rY;
this.eventMove = e;
//var target = this.getTargetFromEl(e.target);
var dims = this.dimsFromTarget(e.target, rX, rY);
let targetModel = this.getTargetModel(this.target);
this.selectTargetModel(targetModel);
@ -458,14 +475,14 @@ module.exports = Backbone.View.extend({
droppable = droppable instanceof Backbone.Collection ? 1 : droppable;
droppable = droppable instanceof Array ? droppable.join(', ') : droppable;
result.dropInfo = droppable;
droppable = typeof droppable === 'string' ? src.matches(droppable) : droppable;
droppable = typeof droppable === 'string' ? this.matches(src, droppable) : droppable;
result.droppable = droppable;
// check if the source is draggable in target
let draggable = srcModel.get('draggable');
draggable = draggable instanceof Array ? draggable.join(', ') : draggable;
result.dragInfo = draggable;
draggable = typeof draggable === 'string' ? trg.matches(draggable) : draggable;
draggable = typeof draggable === 'string' ? this.matches(trg, draggable) : draggable;
result.draggable = draggable;
if (!droppable || !draggable) {
@ -490,7 +507,7 @@ module.exports = Backbone.View.extend({
}
// Select the first valuable target
if (!target.matches(`${this.itemSel}, ${this.containerSel}`)) {
if (!this.matches(target, `${this.itemSel}, ${this.containerSel}`)) {
target = this.closest(target, this.itemSel);
}
@ -531,21 +548,110 @@ module.exports = Backbone.View.extend({
// Target when I will drop element to sort
this.target = this.prevTarget;
// Generally also on every new target the poiner enters near
// to borders, so have to to check always
// Generally, on any new target the poiner enters inside its area and
// triggers nearBorders(), so have to take care of this
if(this.nearBorders(this.prevTargetDim, rX, rY) ||
(!this.nested && !this.cacheDims.length)) {
if (!this.validTarget(this.targetP).valid) {
return this.dimsFromTarget(this.targetP, rX, rY);
const targetParent = this.targetP;
if (targetParent && this.validTarget(targetParent).valid) {
dims = this.cacheDimsP;
this.target = targetParent;
}
dims = this.cacheDimsP;
this.target = this.targetP;
}
this.lastPos = null;
return dims;
},
/**
* Get valid target from element
* This method should replace dimsFromTarget()
* @param {HTMLElement} el
* @return {HTMLElement}
*/
getTargetFromEl(el) {
let target = el;
let targetParent;
let targetPrev = this.targetPrev;
const containerSel = this.containerSel;
// Select the first valuable target
if (!this.matches(target, `${this.itemSel}, ${containerSel}`)) {
target = this.closest(target, this.itemSel);
}
// If draggable is an array the target will be one of those
// TODO check if this options is used somewhere
if (this.draggable instanceof Array) {
target = this.closest(target, this.draggable.join(','));
}
// Check if the target is different from the previous one
if (targetPrev && targetPrev != target) {
this.targetPrev = '';
}
// New target found
if (!this.targetPrev) {
targetParent = this.closest(target, containerSel);
// If the current target is not valid (src/trg reasons) try with
// the parent one (if exists)
if (!this.validTarget(target).valid && targetParent) {
return this.getTargetFromEl(targetParent);
}
this.targetPrev = target;
}
// Generally, on any new target the poiner enters inside its area and
// triggers nearBorders(), so have to take care of this
if (this.nearElBorders(target)) {
targetParent = this.closest(target, containerSel);
if (targetParent && this.validTarget(targetParent).valid) {
target = targetParent;
}
}
return target;
},
/**
* Check if the current pointer is neare to element borders
* @return {Boolen}
*/
nearElBorders(el) {
const off = 10;
const rect = el.getBoundingClientRect();
const body = el.ownerDocument.body;
const {x, y} = this.getCurrentPos();
const top = rect.top + body.scrollTop;
const left = rect.left + body.scrollLeft;
const width = rect.width;
const height = rect.height;
//console.log(pos, {top, left});
if ( y < (top + off) || // near top edge
y > (top + height - off) || // near bottom edge
x < (left + off) || // near left edge
x > (left + width - off) // near right edge
) {
return 1;
}
},
getCurrentPos() {
const ev = this.eventMove;
const x = ev.pageX || 0;
const y = ev.pageY || 0;
return {x, y};
},
/**
* Returns dimensions and positions about the element
* @param {HTMLElement} el
@ -599,7 +705,7 @@ module.exports = Backbone.View.extend({
for (var i = 0, len = ch.length; i < len; i++) {
var el = ch[i];
if (!el.matches(this.itemSel)) {
if (!this.matches(el, this.itemSel)) {
continue;
}
@ -755,9 +861,13 @@ module.exports = Backbone.View.extend({
* */
endMove(e) {
var created;
this.$el.off('mousemove', this.onMove);
this.$document.off('mouseup', this.endMove);
this.$document.off('keydown', this.rollback);
const docs = this.getDocuments();
const container = this.getContainerEl();
off(container, 'mousemove', this.onMove);
off(docs, 'mouseup', this.endMove);
off(docs, 'keydown', this.rollback);
//this.$document.off('mouseup', this.endMove);
//this.$document.off('keydown', this.rollback);
this.plh.style.display = 'none';
var clsReg = new RegExp('(?:^|\\s)'+this.freezeClass+'(?!\\S)', 'gi');
let src = this.eV;
@ -867,15 +977,13 @@ module.exports = Backbone.View.extend({
* @param {Bool} Indicates if rollback in anycase
* */
rollback(e) {
$(document).off('keydown', this.rollback);
this.$document.off('keydown', this.rollback);
var key = e.which || e.keyCode;
off(this.getDocuments(), 'keydown', this.rollback);
const key = e.which || e.keyCode;
if (key == 27) {
this.moved = false;
this.moved = 0;
this.endMove();
}
return;
},
});

8
src/utils/extender.js

@ -51,7 +51,7 @@ module.exports = ({$, Backbone}) => {
};
}
if ($) {
if ($ && $.prototype.constructor.name !== 'jQuery') {
const fn = $.fn;
const splitNamespace = function(name) {
@ -293,8 +293,10 @@ module.exports = ({$, Backbone}) => {
return ar;
}
$.inArray = function(val, arr) {
return arr.indexOf(val);
const indexOf = Array.prototype.indexOf;
$.inArray = function(val, arr, i) {
return arr == null ? -1 : indexOf.call( arr, val, i );
}
$.Event = function(src, props) {

3
src/utils/fetch.js

@ -24,6 +24,7 @@ export default typeof fetch == 'function' ? fetch.bind() : (url, options) => {
req.upload.onprogress = options.onProgress;
}
req.send(options.body);
// Include body only if present
options.body ? req.send(options.body) : req.send();
});
}

24
src/utils/mixins.js

@ -7,6 +7,7 @@ const on = (el, ev, fn) => {
}
}
const off = (el, ev, fn) => {
ev = ev.split(/\s+/);
el = el instanceof Array ? el : [el];
@ -16,4 +17,25 @@ const off = (el, ev, fn) => {
}
}
export {on, off}
const getUnitFromValue = (value) => {
return value.replace(parseFloat(value), '');
}
const upFirst = value => value[0].toUpperCase() + value.toLowerCase().slice(1);
const camelCase = value => {
const values = value.split('-');
return values[0].toLowerCase() + values.slice(1).map(upFirst);
}
export {
on,
off,
upFirst,
camelCase,
getUnitFromValue
}

3
test/specs/css_composer/e2e/CssComposer.js

@ -56,8 +56,7 @@ module.exports = {
it('Rules are correctly imported from default property', () => {
var gj = grapesjs.init({
stylePrefix: '',
storageManager: {autoload: 0, type:'none' },
assetManager: {storageType: 'none', },
storageManager: {autoload: 0, type:'none'},
cssComposer: {rules: rulesSet},
container: 'csscomposer-fixture',
});

193
test/specs/dom_components/model/Component.js

@ -6,22 +6,27 @@ const ComponentLink = require('dom_components/model/ComponentLink');
const ComponentMap = require('dom_components/model/ComponentMap');
const ComponentVideo = require('dom_components/model/ComponentVideo');
const Components = require('dom_components/model/Components');
const Selector = require('selector_manager/model/Selector');
const Editor = require('editor/model/Editor');
const $ = Backbone.$;
module.exports = {
run() {
var obj;
var dcomp;
var compOpts;
let obj;
let dcomp;
let compOpts;
let em;
describe('Component', () => {
beforeEach(() => {
obj = new Component();
em = new Editor({});
dcomp = new DomComponents();
compOpts = {
em,
componentTypes: dcomp.componentTypes,
};
obj = new Component({}, compOpts);
});
afterEach(() => {
@ -116,6 +121,153 @@ module.exports = {
expect(obj).toEqual({tagName: 'span'});
});
it('setClass single class string', () => {
obj.setClass('class1');
const result = obj.get('classes').models;
expect(result.length).toEqual(1);
expect(result[0] instanceof Selector).toEqual(true);
expect(result[0].get('name')).toEqual('class1');
});
it('setClass multiple class string', () => {
obj.setClass('class1 class2');
const result = obj.get('classes').models;
expect(result.length).toEqual(2);
});
it('setClass single class array', () => {
obj.setClass(['class1']);
const result = obj.get('classes').models;
expect(result.length).toEqual(1);
});
it('setClass multiple class array', () => {
obj.setClass(['class1', 'class2']);
const result = obj.get('classes').models;
expect(result.length).toEqual(2);
});
it('addClass multiple array', () => {
obj.addClass(['class1', 'class2']);
const result = obj.get('classes').models;
expect(result.length).toEqual(2);
});
it('addClass avoid same name classes', () => {
obj.addClass(['class1', 'class2']);
obj.addClass(['class1', 'class3']);
const result = obj.get('classes').models;
expect(result.length).toEqual(3);
});
it('setAttributes', () => {
obj.setAttributes({
id: 'test',
'data-test': 'value',
class: 'class1 class2',
style: 'color: white; background: #fff'
});
expect(obj.getAttributes()).toEqual({
id: 'test',
'data-test': 'value',
});
expect(obj.get('classes').length).toEqual(2);
expect(obj.getStyle()).toEqual({
color: 'white',
background: '#fff',
});
});
it('setAttributes overwrites correctly', () => {
obj.setAttributes({id: 'test', 'data-test': 'value'});
obj.setAttributes({'data-test': 'value2'});
expect(obj.getAttributes()).toEqual({'data-test': 'value2'});
});
it('append() returns always an array', () => {
let result = obj.append('<span>text1</span>');
expect(result.length).toEqual(1);
result = obj.append('<span>text1</span><div>text2</div>');
expect(result.length).toEqual(2);
});
it('append() new components as string', () => {
obj.append('<span>text1</span><div>text2</div>');
const comps = obj.components();
expect(comps.length).toEqual(2);
expect(comps.models[0].get('tagName')).toEqual('span');
expect(comps.models[1].get('tagName')).toEqual('div');
});
it('append() new components as Objects', () => {
obj.append([{}, {}]);
const comps = obj.components();
expect(comps.length).toEqual(2);
obj.append({});
expect(comps.length).toEqual(3);
});
it('components() set new collection', () => {
obj.append([{}, {}]);
obj.components('<span>test</div>');
const result = obj.components();
expect(result.length).toEqual(1);
expect(result.models[0].get('tagName')).toEqual('span');
});
it('Propagate properties to children', () => {
obj.append({propagate: 'removable'});
const result = obj.components();
const newObj = result.models[0];
expect(newObj.get('removable')).toEqual(true);
newObj.set('removable', false);
newObj.append({draggable: false});
const child = newObj.components().models[0];
expect(child.get('removable')).toEqual(false);
expect(child.get('propagate')).toEqual(['removable']);
});
it('Ability to stop/change propagation chain', () => {
obj.append({
removable: false,
draggable: false,
propagate: ['removable', 'draggable']
});
const result = obj.components();
const newObj = result.models[0];
newObj.components(`
<div id="comp01">
<div id="comp11">comp1</div>
<div id="comp12" data-gjs-stop="1" data-gjs-removable="true" data-gjs-draggable="true" data-gjs-propagate='["stop"]'>
<div id="comp21">comp21</div>
<div id="comp22">comp22</div>
</div>
<div id="comp13">
<div id="comp31">comp31</div>
<div id="comp32">comp32</div>
</div>
</div>
<div id="comp02">TEST</div>`);
const notInhereted = model => {
expect(model.get('stop')).toEqual(1);
expect(model.get('removable')).toEqual(true);
expect(model.get('draggable')).toEqual(true);
expect(model.get('propagate')).toEqual(['stop']);
model.components().each(model => inhereted(model))
};
const inhereted = model => {
if (model.get('stop')) {
notInhereted(model);
} else {
expect(model.get('removable')).toEqual(false);
expect(model.get('draggable')).toEqual(false);
expect(model.get('propagate')).toEqual(['removable', 'draggable']);
model.components().each(model => inhereted(model))
}
}
newObj.components().each(model => inhereted(model))
});
});
describe('Image Component', () => {
@ -200,12 +352,39 @@ module.exports = {
describe('Link Component', () => {
it('Component parse a element', () => {
var el = document.createElement('a');
obj = ComponentLink.isComponent(el);
const aEl = document.createElement('a');
it('Component parse link element', () => {
obj = ComponentLink.isComponent(aEl);
expect(obj).toEqual({type: 'link'});
});
it('Component parse link element with text content', () => {
aEl.innerHTML = 'some text here ';
obj = ComponentLink.isComponent(aEl);
expect(obj).toEqual({type: 'link'});
});
it('Component parse link element with not only text content', () => {
aEl.innerHTML = '<div>Some</div> text <div>here </div>';
obj = ComponentLink.isComponent(aEl);
expect(obj).toEqual({type: 'link'});
});
it('Component parse link element with only not text content', () => {
aEl.innerHTML = `<div>Some</div>
<div>text</div>
<div>here </div>`;
obj = ComponentLink.isComponent(aEl);
expect(obj).toEqual({type: 'link', editable: 0});
});
it('Link element with only an image inside is not editable', () => {
aEl.innerHTML = '<img src="##"/>';
obj = ComponentLink.isComponent(aEl);
expect(obj).toEqual({type: 'link', editable: 0});
});
});
describe('Map Component', () => {

9
test/specs/panels/index.js

@ -93,6 +93,15 @@ describe('Panels', () => {
expect(spy.called).toEqual(true);
});
it("Disable correctly buttons flagged as disabled", () => {
var spy = sinon.spy();
var panel = obj.addPanel({id: 'test'});
var btn = obj.addButton('test', {id:'btn', disable: true});
btn.on('change:disable', spy);
obj.disableButtons();
expect(spy.called).toEqual(true);
});
});
Models.run();

22
test/specs/panels/model/PanelModels.js

@ -33,6 +33,10 @@ module.exports = {
expect(obj.get('buttons').length).toEqual(1);
});
it('Has a disable attribute with default value as false', () => {
expect(obj.get('disable')).toEqual(false);
});
});
describe('Buttons', () => {
@ -69,6 +73,24 @@ module.exports = {
obj.deactivateAllExceptOne(btn);
expect(obj.at(0).get('active')).toEqual(true);
});
it('Disable all buttons', () => {
obj.add({ disable: false });
obj.disableAllButtons();
expect(obj.at(0).get('disable')).toEqual(true);
});
it('Disables buttons with context', () => {
obj.add({ disable: false, context: 'someContext' });
obj.disableAllButtons('someContext');
expect(obj.at(0).get('disable')).toEqual(true);
});
it('Disables except one', () => {
var btn = obj.add({ disable: false });
obj.disableAllButtonsExceptOne(btn);
expect(obj.at(0).get('disable')).toEqual(false);
});
});

31
test/specs/panels/view/ButtonView.js

@ -14,7 +14,7 @@ module.exports = {
beforeEach(() => {
model = new Button();
view = new ButtonView({
model
model: model
});
document.body.innerHTML = '<div id="fixtures"></div>';
fixtures = document.body.querySelector('#fixtures');
@ -55,6 +55,35 @@ module.exports = {
expect(view.el.getAttribute('class')).toEqual(btnClass);
});
it('Disable the button', () => {
model.set('disable', true, {silent: true});
view.updateDisable();
expect(view.el.getAttribute('class')).toEqual(btnClass + ' active');
});
it('Enable the disabled button', () => {
model.set('disable', true, {silent: true});
view.updateDisable();
expect(view.el.getAttribute('class')).toEqual(btnClass + ' active');
model.set('disable', false, {silent: true});
view.updateDisable();
expect(view.el.getAttribute('class')).toEqual(btnClass);
});
it('Cancels the click action when button is disabled', () => {
const stub = sinon.stub(view, 'toogleActive');
model.set('disable', true, {silent: true});
view.clicked();
expect(stub.called).toEqual(false);
});
it('Enable the click action when button is enable', () => {
const stub = sinon.stub(view, 'toogleActive');
model.set('disable', false, {silent: true});
view.clicked();
expect(stub.called).toEqual(true);
});
it('Renders correctly', () => {
expect(view.render()).toExist();
});

28
test/specs/parser/model/ParserHtml.js

@ -382,6 +382,34 @@ module.exports = {
expect(obj.parse(str).html).toEqual(result);
});
it('Parse attributes with object inside', () => {
var str = `<div data-gjs-test='{ "prop1": "value1", "prop2": 10, "prop3": true}'>test2 </div>`;
var result = {
tagName: 'div',
attributes: {},
type: 'text',
test: {
prop1: 'value1',
prop2: 10,
prop3: true,
},
content: 'test2 ',
};
expect(obj.parse(str).html).toEqual(result);
});
it('Parse attributes with arrays inside', () => {
var str = `<div data-gjs-test='["value1", "value2"]'>test2 </div>`;
var result = {
tagName: 'div',
attributes: {},
type: 'text',
test: ['value1', 'value2'],
content: 'test2 ',
};
expect(obj.parse(str).html).toEqual(result);
});
});
}

31
test/specs/selector_manager/index.js

@ -1,4 +1,5 @@
var SelectorManager = require('selector_manager');
var Selector = require('selector_manager/model/Selector');
var Models = require('./model/SelectorModels');
var ClassTagView = require('./view/ClassTagView');
var ClassTagsView = require('./view/ClassTagsView');
@ -75,6 +76,36 @@ describe('SelectorManager', () => {
expect(obj.get('test')).toEqual(undefined);
});
it('addClass single class string', () => {
const result = obj.addClass('class1');
expect(result.length).toEqual(1);
expect(result[0] instanceof Selector).toEqual(true);
expect(result[0].get('name')).toEqual('class1');
});
it('addClass multiple class string', () => {
const result = obj.addClass('class1 class2');
expect(result.length).toEqual(2);
expect(obj.getAll().length).toEqual(2);
});
it('addClass single class array', () => {
const result = obj.addClass(['class1']);
expect(result.length).toEqual(1);
});
it('addClass multiple class array', () => {
const result = obj.addClass(['class1', 'class2']);
expect(result.length).toEqual(2);
});
it('addClass Avoid same name classes', () => {
obj.addClass('class1');
const result = obj.addClass('class1');
expect(obj.getAll().length).toEqual(1);
expect(result.length).toEqual(1);
});
});
Models.run();

87
test/specs/style_manager/model/Models.js

@ -1,6 +1,7 @@
const Sector = require('style_manager/model/Sector');
const Sectors = require('style_manager/model/Sectors');
const Property = require('style_manager/model/Property');
const PropertyInteger = require('style_manager/model/PropertyInteger');
const Properties = require('style_manager/model/Properties');
const Layer = require('style_manager/model/Layer');
const Layers = require('style_manager/model/Layers');
@ -125,7 +126,6 @@ module.exports = {
});
describe('Property', () => {
var obj;
beforeEach(() => {
@ -140,6 +140,59 @@ module.exports = {
expect(obj.has('property')).toEqual(true);
});
it('parseValue', () => {
const result = { value: 'testValue' };
expect(obj.parseValue('testValue')).toEqual(result);
});
it('parseValue with function but without functionName', () => {
const result = { value: 'fn(testValue)' };
expect(obj.parseValue('fn(testValue)')).toEqual(result);
});
it('parseValue with function and functionName', () => {
obj = new Property({ functionName: 'fn' });
const result = { value: 'testValue' };
expect(obj.parseValue('fn(testValue)')).toEqual(result);
expect(obj.parseValue('fn(testValue')).toEqual(result);
});
});
describe('PropertyInteger', () => {
var obj;
beforeEach(() => {
obj = new PropertyInteger({units: ['px', 'deg']});
});
afterEach(() => {
obj = null;
});
it('parseValue with units', () => {
const result = { value: 20, unit: 'px' };
expect(obj.parseValue('20px')).toEqual(result);
});
it('parse input value with function', () => {
obj = new PropertyInteger({units: ['px', 'deg'], functionName: 'test'});
const result = { value: 55, unit: 'deg' };
expect(obj.parseValue('test(55deg)')).toEqual(result);
});
it('parse input value with min', () => {
obj = new PropertyInteger({units: ['px'], min: 10});
const result = { value: 10, unit: 'px' };
expect(obj.parseValue('1px')).toEqual(result);
expect(obj.parseValue('15px')).toEqual({ value: 15, unit: 'px' });
});
it('parse input value with max', () => {
obj = new PropertyInteger({units: ['px'], max: 100});
const result = { value: 100, unit: 'px' };
expect(obj.parseValue('200px')).toEqual(result);
expect(obj.parseValue('95px')).toEqual({ value: 95, unit: 'px' });
});
});
describe('Properties', () => {
@ -409,24 +462,28 @@ module.exports = {
type : 'integer',
units : ['px','%'],
defaults : 0,
min: 0,
},{
property : 'padding-right',
fixedValues: ['initial', 'inherit', 'auto'],
type : 'integer',
units : ['px','%'],
defaults : 0,
min: 0,
},{
property : 'padding-bottom',
fixedValues: ['initial', 'inherit', 'auto'],
type : 'integer',
units : ['px','%'],
defaults : 0,
min: 0,
},{
property : 'padding-left',
fixedValues: ['initial', 'inherit', 'auto'],
type : 'integer',
units : ['px','%'],
defaults : 0,
min: 0,
},],
};
expect(obj.build('padding')).toEqual([res]);
@ -435,26 +492,24 @@ module.exports = {
it('Build font-family', () => {
var ss = ', sans-serif';
var ms = ', monospace';
var ff = 'font-family: ';
var sty = '; font-size:15px';
var res = {
property: 'font-family',
type: 'select',
defaults: 'Arial, Helvetica' + ss,
list:[
{name: 'Arial', value: 'Arial, Helvetica' + ss, style: ff + 'Arial, Helvetica' + ss + sty},
{name: 'Arial Black', value: 'Arial Black, Gadget' + ss, style: ff + 'Arial Black, Gadget' + ss + sty},
{name: 'Brush Script MT', value: 'Brush Script MT' + ss, style: ff + 'Brush Script MT' + ss + sty},
{name: 'Comic Sans MS', value: 'Comic Sans MS, cursive' + ss, style: ff + 'Comic Sans MS, cursive' + ss + sty},
{name: 'Courier New', value: 'Courier New, Courier' + ms, style: ff + 'Courier New, Courier' + ms + sty},
{name: 'Georgia', value: 'Georgia, serif', style: ff + 'Georgia, serif' + sty},
{name: 'Helvetica', value: 'Helvetica, serif', style: ff + 'Helvetica, serif' + sty},
{name: 'Impact', value: 'Impact, Charcoal' + ss, style: ff + 'Impact, Charcoal' + ss + sty},
{name: 'Lucida Sans Unicode', value: 'Lucida Sans Unicode, Lucida Grande' + ss, style: ff + 'Lucida Sans Unicode, Lucida Grande' + ss + sty},
{name: 'Tahoma', value: 'Tahoma, Geneva' + ss, style: ff + 'Tahoma, Geneva' + ss + sty},
{name: 'Times New Roman', value: 'Times New Roman, Times, serif', style: ff + 'Times New Roman, Times, serif' + sty},
{name: 'Trebuchet MS', value: 'Trebuchet MS, Helvetica' + ss, style: ff + 'Trebuchet MS, Helvetica' + ss + sty},
{name: 'Verdana', value: 'Verdana, Geneva' + ss, style: ff + 'Verdana, Geneva' + ss + sty},
{name: 'Arial', value: 'Arial, Helvetica' + ss},
{name: 'Arial Black', value: 'Arial Black, Gadget' + ss},
{name: 'Brush Script MT', value: 'Brush Script MT' + ss},
{name: 'Comic Sans MS', value: 'Comic Sans MS, cursive' + ss},
{name: 'Courier New', value: 'Courier New, Courier' + ms},
{name: 'Georgia', value: 'Georgia, serif'},
{name: 'Helvetica', value: 'Helvetica, serif'},
{name: 'Impact', value: 'Impact, Charcoal' + ss},
{name: 'Lucida Sans Unicode', value: 'Lucida Sans Unicode, Lucida Grande' + ss},
{name: 'Tahoma', value: 'Tahoma, Geneva' + ss},
{name: 'Times New Roman', value: 'Times New Roman, Times, serif'},
{name: 'Trebuchet MS', value: 'Trebuchet MS, Helvetica' + ss},
{name: 'Verdana', value: 'Verdana, Geneva' + ss},
],
};
expect(obj.build('font-family')).toEqual([res]);

16
test/specs/style_manager/view/PropertyIntegerView.js

@ -1,5 +1,6 @@
const PropertyIntegerView = require('style_manager/view/PropertyIntegerView');
const Property = require('style_manager/model/Property');
const PropertyInteger = require('style_manager/model/PropertyInteger');
const Component = require('dom_components/model/Component');
module.exports = {
@ -26,8 +27,7 @@ module.exports = {
beforeEach(() => {
target = new Component();
component = new Component();
model = new Property({
type: 'integer',
model = new PropertyInteger({
units,
property: propName
});
@ -111,12 +111,9 @@ module.exports = {
});
it('Update target on value change', () => {
view.selectedComponent = component;
view.model.set('value', intValue);
var compStyle = view.selectedComponent.get('style');
var assertStyle = {};
assertStyle[propName] = intValue;
expect(compStyle).toEqual(assertStyle);
const val = `${intValue}%`;
view.model.setValue(val);
expect(view.getTargetValue()).toEqual(val);
});
describe('With target setted', () => {
@ -161,8 +158,7 @@ module.exports = {
beforeEach(() => {
component = new Component();
model = new Property({
type: 'integer',
model = new PropertyInteger({
units,
property: propName,
defaults: intValue,

4
test/specs/style_manager/view/PropertyRadioView.js

@ -67,12 +67,12 @@ module.exports = {
});
it('Options rendered', () => {
var input = view.el.querySelector('#input-holder').firstChild;
var input = view.el.querySelector('.field').firstChild;
expect(input.children.length).toEqual(options.length);
});
it('Options rendered correctly', () => {
var children = view.el.querySelector('#input-holder').firstChild.children;
var children = view.el.querySelector('.field').firstChild.children;
expect(children[0].querySelector('label').textContent).toEqual('test1value');
expect(children[1].querySelector('label').textContent).toEqual('test2');
expect(children[0].querySelector('input').value).toEqual(options[0].value);

7
test/specs/style_manager/view/PropertyView.js

@ -133,11 +133,10 @@ module.exports = {
it('Fetch value from function', () => {
view.selectedComponent = component;
var style = {};
style[propName] = 'testfun(' + propValue + ')';
component.set('style', style);
const val = `testfun(${propValue})`;
component.set('style', {[propName]: val});
view.model.set('functionName', 'testfun');
expect(view.getTargetValue()).toEqual(propValue);
expect(view.getTargetValue()).toEqual(val);
});
describe('With target setted', () => {

13
webpack.config.js

@ -1,10 +1,11 @@
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var pkg = require('./package.json');
var env = process.env.WEBPACK_ENV;
var webpack = require('webpack');
var fs = require('fs');
var name = 'grapes';
var plugins = [];
if(env !== 'dev') {
if (process.env.WEBPACK_ENV !== 'dev') {
plugins = [
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.optimize.UglifyJsPlugin({
@ -13,6 +14,12 @@ if(env !== 'dev') {
}),
new webpack.BannerPlugin(pkg.name + ' - ' + pkg.version),
]
} else {
var index = 'index.html';
var indexDev = '_' + index;
plugins.push(new HtmlWebpackPlugin({
template: fs.existsSync(indexDev) ? indexDev : index
}));
}
plugins.push(new webpack.ProvidePlugin({

6293
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save