Browse Source

Merge pull request #1918 from artf/dev

Merge dev
pull/2161/head
Artur Arseniev 8 years ago
committed by GitHub
parent
commit
8d43bd1f97
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dist/css/grapes.min.css
  2. 754
      dist/grapes.js
  3. 8
      dist/grapes.min.js
  4. 2
      dist/grapes.min.js.map
  5. 2
      docs/.vuepress/components/demos/DemoCanvasOnly.js
  6. 60
      docs/api/canvas.md
  7. 22
      docs/api/component.md
  8. 7
      docs/api/editor.md
  9. 1
      docs/api/keymaps.md
  10. 2
      package-lock.json
  11. 2
      package.json
  12. 13
      src/canvas/index.js
  13. 5
      src/canvas/view/CanvasView.js
  14. 4
      src/commands/config/config.js
  15. 5
      src/commands/index.js
  16. 2
      src/commands/view/CommandAbstract.js
  17. 299
      src/commands/view/ComponentDrag.js
  18. 66
      src/commands/view/Preview.js
  19. 4
      src/dom_components/index.js
  20. 1
      src/dom_components/model/ComponentMap.js
  21. 9
      src/dom_components/view/ComponentTextNodeView.js
  22. 64
      src/dom_components/view/ComponentsView.js
  23. 2
      src/editor/index.js
  24. 2
      src/editor/model/Editor.js
  25. 39
      src/modal_dialog/index.js
  26. 4
      src/panels/config/config.js
  27. 2
      src/panels/index.js
  28. 15
      src/panels/model/Buttons.js
  29. 18
      src/panels/view/ButtonView.js
  30. 10
      src/storage_manager/config/config.js
  31. 15
      src/storage_manager/model/RemoteStorage.js
  32. 2
      src/style_manager/model/Sector.js
  33. 90
      src/styles/scss/_gjs_canvas.scss
  34. 8
      src/styles/scss/main.scss
  35. 113
      src/utils/Dragger.js
  36. 7
      src/utils/Sorter.js
  37. 17
      test/specs/commands/view/CommandAbstract.js
  38. 17
      test/specs/storage_manager/model/Models.js

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

754
dist/grapes.js

File diff suppressed because it is too large

8
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
dist/grapes.min.js.map

File diff suppressed because one or more lines are too long

2
docs/.vuepress/components/demos/DemoCanvasOnly.js

@ -8,7 +8,7 @@ const editor = grapesjs.init({
height: '300px',
width: 'auto',
// Disable the storage manager for the moment
storageManager: { type: null },
storageManager: false,
// Avoid any default panel
panels: { defaults: [] },
});

60
docs/api/canvas.md

@ -28,30 +28,32 @@ const canvas = editor.Canvas;
- [setCustomBadgeLabel][9]
- [hasFocus][10]
- [scrollTo][11]
- [setZoom][12]
- [getZoom][13]
## getConfig
Get the configuration object
Returns **[Object][12]**
Returns **[Object][14]**
## getElement
Get the canvas element
Returns **[HTMLElement][13]**
Returns **[HTMLElement][15]**
## getFrameEl
Get the iframe element of the canvas
Returns **[HTMLIFrameElement][14]**
Returns **[HTMLIFrameElement][16]**
## getWindow
Get the window instance of the iframe element
Returns **[Window][15]**
Returns **[Window][17]**
## getDocument
@ -63,13 +65,13 @@ Returns **HTMLDocument**
Get the body of the iframe element
Returns **[HTMLBodyElement][16]**
Returns **[HTMLBodyElement][18]**
## getWrapperEl
Get the wrapper element containing all the components
Returns **[HTMLElement][13]**
Returns **[HTMLElement][15]**
## setCustomBadgeLabel
@ -77,7 +79,7 @@ Set custom badge naming strategy
### Parameters
- `f` **[Function][17]**
- `f` **[Function][19]**
### Examples
@ -91,7 +93,7 @@ canvas.setCustomBadgeLabel(function(component){
Check if the canvas is focused
Returns **[Boolean][18]**
Returns **[Boolean][20]**
## scrollTo
@ -102,9 +104,9 @@ passed to it. For instance, you can scroll smoothly by using
### Parameters
- `el` **([HTMLElement][13] | Component)**
- `opts` **[Object][12]** Options, same as options for `scrollIntoView` (optional, default `{}`)
- `opts.force` **[Boolean][18]** Force the scroll, even if the element is already visible (optional, default `false`)
- `el` **([HTMLElement][15] | Component)**
- `opts` **[Object][14]** Options, same as options for `scrollIntoView` (optional, default `{}`)
- `opts.force` **[Boolean][20]** Force the scroll, even if the element is already visible (optional, default `false`)
### Examples
@ -116,6 +118,22 @@ canvas.scrollTo(selected, { behavior: 'smooth' });
canvas.scrollTo(selected, { force: true });
```
## setZoom
Set zoom value
### Parameters
- `value` **[Number][21]** The zoom value, from 0 to 100
Returns **this**
## getZoom
Get zoom value
Returns **[Number][21]**
[1]: https://github.com/artf/grapesjs/blob/master/src/canvas/config/config.js
[2]: #getconfig
@ -138,16 +156,22 @@ canvas.scrollTo(selected, { force: true });
[11]: #scrollto
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[12]: #setzoom
[13]: #getzoom
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[15]: https://developer.mozilla.org/docs/Web/HTML/Element
[13]: https://developer.mozilla.org/docs/Web/HTML/Element
[16]: https://developer.mozilla.org/docs/Web/API/HTMLIFrameElement
[14]: https://developer.mozilla.org/docs/Web/API/HTMLIFrameElement
[17]: https://developer.mozilla.org/docs/Web/API/Window
[15]: https://developer.mozilla.org/docs/Web/API/Window
[18]: https://developer.mozilla.org/docs/Web/HTML/Element/body
[16]: https://developer.mozilla.org/docs/Web/HTML/Element/body
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

22
docs/api/component.md

@ -454,6 +454,28 @@ Remove the component
Returns **this**
## getList
The list of components is taken from the Components module.
Initially, the list, was set statically on the Component object but it was
not ok, as it was shared between multiple editor instances
### Parameters
- `model`
## checkId
This method checks, for each parsed component and style object
(are not Components/CSSRules yet), for duplicated id and fixes them
This method is used in Components.js just after the parsing
### Parameters
- `components`
- `styles` (optional, default `[]`)
- `list` (optional, default `{}`)
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

7
docs/api/editor.md

@ -35,6 +35,8 @@ editor.on('EVENT-NAME', (some, argument) => {
- `component:selected` - New component selected, the selected model is passed as an argument to the callback
- `component:deselected` - Component deselected, the deselected model is passed as an argument to the callback
- `component:toggled` - Component selection changed, toggled model is passed as an argument to the callback
- `component:type:add` - New component type added, the new type is passed as an argument to the callback
- `component:type:update` - Component type updated, the updated type is passed as an argument to the callback
### Blocks
@ -113,10 +115,11 @@ editor.on('EVENT-NAME', (some, argument) => {
### General
- `canvasScroll` - Triggered when the canvas is scrolle
- `canvasScroll` - Canvas is scrolled
- `update` - The structure of the template is updated (its HTML/CSS)
- `undo` - Undo executed
- `redo` - Redo executed
- `load` - When the editor is loaded
- `load` - Editor is loaded
## getConfig

1
docs/api/keymaps.md

@ -47,6 +47,7 @@ Add new keymap
- `id` **[string][8]** Keymap id
- `keys` **[string][8]** Keymap keys, eg. `ctrl+a`, `⌘+z, ctrl+z`
- `handler` **([Function][9] \| [string][8])** Keymap handler, might be a function
- `opts` **[Object][7]** Options (optional, default `{}`)
### Examples

2
package-lock.json

@ -1,6 +1,6 @@
{
"name": "grapesjs",
"version": "0.14.55",
"version": "0.14.57",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

2
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.14.55",
"version": "0.14.57",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",

13
src/canvas/index.js

@ -302,6 +302,19 @@ module.exports = () => {
return CanvasView.getElementOffsets(el);
},
/**
* Get canvas rectangular data
* @returns {Object}
*/
getRect() {
const { top, left } = CanvasView.getPosition();
return {
...CanvasView.getCanvasOffset(),
topScroll: top,
leftScroll: left
};
},
/**
* This method comes handy when you need to attach something like toolbars
* to elements inside the canvas, dealing with all relative position,

5
src/canvas/view/CanvasView.js

@ -73,7 +73,7 @@ module.exports = Backbone.View.extend({
}
},
updateFrames() {
updateFrames(ev) {
const { em, model } = this;
const { x, y } = model.attributes;
const zoom = this.getZoom();
@ -84,6 +84,7 @@ module.exports = Backbone.View.extend({
this.clearOff();
this.onFrameScroll();
em.stopDefault(defOpts);
em.trigger('canvas:update', ev);
timerZoom && clearTimeout(timerZoom);
timerZoom = setTimeout(() => em.runDefault(defOpts));
},
@ -353,7 +354,7 @@ module.exports = Backbone.View.extend({
const height = eo.height * zoom;
const width = eo.width * zoom;
return { top, left, height, width };
return { top, left, height, width, zoom, rect: eo };
},
/**

4
src/commands/config/config.js

@ -5,6 +5,10 @@ module.exports = {
defaults: [],
// If true, stateful commands (with `run` and `stop` methods) can't be runned multiple times.
// So, if the command is already active, running it again will not execute the `run` method
strict: 1,
// Editor model
// @deprecated
em: null,

5
src/commands/index.js

@ -144,6 +144,7 @@ module.exports = () => {
if (includes(modes, mode)) {
// TODO move grabbing func in editor/canvas from the Sorter
dragger = editor.runCommand('core:component-drag', {
guidesInfo: 1,
mode,
target: sel,
onEnd,
@ -330,7 +331,7 @@ module.exports = () => {
const id = command.id;
const editor = em.get('Editor');
if (!this.isActive(id) || options.force) {
if (!this.isActive(id) || options.force || !c.strict) {
if (id && command.stop && !command.noStop) {
active[id] = result;
}
@ -355,7 +356,7 @@ module.exports = () => {
const id = command.id;
const editor = em.get('Editor');
if (this.isActive(id) || options.force) {
if (this.isActive(id) || options.force || !c.strict) {
if (id) delete active[id];
result = command.callStop(editor, options);
}

2
src/commands/view/CommandAbstract.js

@ -109,6 +109,7 @@ export default Backbone.View.extend({
const sender = options.sender || editor;
const result = this.run(editor, sender, options);
editor.trigger(`run:${id}`, result, options);
editor.trigger('run', id, result, options);
return result;
},
@ -124,6 +125,7 @@ export default Backbone.View.extend({
editor.trigger(`stop:${id}:before`, options);
const result = this.stop(editor, sender, options);
editor.trigger(`stop:${id}`, result, options);
editor.trigger('stop', id, result, options);
return result;
},

299
src/commands/view/ComponentDrag.js

@ -1,10 +1,20 @@
import { keys, bindAll } from 'underscore';
import { keys, bindAll, each, isUndefined } from 'underscore';
import Dragger from 'utils/Dragger';
module.exports = {
run(editor, sender, opts = {}) {
bindAll(this, 'setPosition', 'onStart', 'onEnd', 'getPosition');
const { target, event, mode } = opts;
bindAll(
this,
'setPosition',
'onStart',
'onDrag',
'onEnd',
'getPosition',
'getGuidesStatic',
'renderGuide',
'getGuidesTarget'
);
const { target, event, mode, dragger = {} } = opts;
const { Canvas } = editor;
const el = target.getEl();
const scale = Canvas.getZoomMultiplier();
@ -13,32 +23,215 @@ module.exports = {
doc: el.ownerDocument,
onStart: this.onStart,
onEnd: this.onEnd,
onDrag: this.onDrag,
getPosition: this.getPosition,
setPosition: this.setPosition
setPosition: this.setPosition,
guidesStatic: () => this.guidesStatic,
guidesTarget: () => this.guidesTarget,
...dragger
};
this.setupGuides();
this.opts = opts;
this.editor = editor;
this.em = editor.getModel();
this.target = target;
this.isTran = mode == 'translate';
let dragger = this.dragger;
this.guidesContainer = this.getGuidesContainer();
this.guidesTarget = this.getGuidesTarget();
this.guidesStatic = this.getGuidesStatic();
window.guidesTarget = this.guidesTarget;
let drg = this.dragger;
if (!dragger) {
dragger = new Dragger(config);
this.dragger = dragger;
if (!drg) {
drg = new Dragger(config);
this.dragger = drg;
} else {
dragger.setOptions(config);
drg.setOptions(config);
}
event && dragger.start(event);
event && drg.start(event);
this.toggleDrag(1);
return dragger;
return drg;
},
stop() {
this.toggleDrag();
},
setupGuides() {
(this.guides || []).forEach(item => {
const { guide } = item;
guide && guide.parentNode.removeChild(guide);
});
this.guides = [];
},
getGuidesContainer() {
let { guidesEl } = this;
if (!guidesEl) {
const { editor, em, opts } = this;
const pfx = editor.getConfig('stylePrefix');
const elInfoX = document.createElement('div');
const elInfoY = document.createElement('div');
const guideContent = `<div class="${pfx}guide-info__line ${pfx}danger-bg">
<div class="${pfx}guide-info__content ${pfx}danger-color"></div>
</div>`;
guidesEl = document.createElement('div');
guidesEl.className = `${pfx}guides`;
elInfoX.className = `${pfx}guide-info ${pfx}guide-info__x`;
elInfoY.className = `${pfx}guide-info ${pfx}guide-info__y`;
elInfoX.innerHTML = guideContent;
elInfoY.innerHTML = guideContent;
guidesEl.appendChild(elInfoX);
guidesEl.appendChild(elInfoY);
editor.Canvas.getToolsEl().appendChild(guidesEl);
this.guidesEl = guidesEl;
this.elGuideInfoX = elInfoX;
this.elGuideInfoY = elInfoY;
this.elGuideInfoContentX = elInfoX.querySelector(
`.${pfx}guide-info__content`
);
this.elGuideInfoContentY = elInfoY.querySelector(
`.${pfx}guide-info__content`
);
em.on('canvas:update', () => {
this.updateGuides();
opts.debug && this.guides.forEach(item => this.renderGuide(item));
});
}
return guidesEl;
},
getGuidesStatic() {
let result = [];
const el = this.target.getEl();
const { parentNode = {} } = el;
each(
parentNode.children,
item =>
(result = result.concat(el !== item ? this.getElementGuides(item) : []))
);
return result.concat(this.getElementGuides(parentNode));
},
getGuidesTarget() {
return this.getElementGuides(this.target.getEl());
},
updateGuides(guides) {
(guides || this.guides).forEach(item => {
const { origin } = item;
const { top, height, left, width } = editor.Canvas.getElementPos(origin);
switch (item.type) {
case 't':
return (item.y = top);
case 'b':
return (item.y = top + height);
case 'l':
return (item.x = left);
case 'r':
return (item.x = left + width);
case 'x':
return (item.x = left + width / 2);
case 'y':
return (item.y = top + height / 2);
}
});
},
getGuidePosUpdate(item, rect) {
const result = {};
const { top, height, left, width } = rect;
switch (item.type) {
case 't':
result.y = top;
break;
case 'b':
result.y = top + height;
break;
case 'l':
result.x = left;
break;
case 'r':
result.x = left + width;
break;
case 'x':
result.x = left + width / 2;
break;
case 'y':
result.y = top + height / 2;
break;
}
return result;
},
renderGuide(item = {}) {
const el = item.guide || document.createElement('div');
const { Canvas } = this.editor;
const { topScroll, top } = Canvas.getRect();
const frameTop = Canvas.getCanvasView().getFrameOffset().top;
// const elRect = this.getGuidePosUpdate(item, el.getBoundingClientRect());
const un = 'px';
const guideSize = item.active ? 2 : 1;
let numEl = el.children[0];
el.style = `position: absolute; background-color: ${
item.active ? 'green' : 'red'
};`;
if (!el.children.length) {
numEl = document.createElement('div');
numEl.style =
'position: absolute; color: red; padding: 5px; top: 0; left: 0;';
el.appendChild(numEl);
}
if (item.y) {
el.style.width = '100%';
el.style.height = `${guideSize}${un}`;
el.style.top = `${item.y}${un}`;
el.style.left = 0;
// numEl.innerHTML = elRect.y;
} else {
el.style.width = `${guideSize}${un}`;
el.style.height = '100%';
el.style.left = `${item.x}${un}`;
el.style.top = `${topScroll - frameTop + top}${un}`;
// numEl.innerHTML = elRect.x;
// numEl.innerHTML = el.style.left;
}
!item.guide && this.guidesContainer.appendChild(el);
return el;
},
getElementGuides(el) {
const { editor, opts } = this;
const { top, height, left, width } = editor.Canvas.getElementPos(el);
const guides = [
{ type: 't', y: top }, // Top
{ type: 'b', y: top + height }, // Bottom
{ type: 'l', x: left }, // Left
{ type: 'r', x: left + width }, // Right
{ type: 'x', x: left + width / 2 }, // Mid x
{ type: 'y', y: top + height / 2 } // Mid y
].map(item => ({
...item,
origin: el,
originRect: editor.Canvas.getElementPos(el),
guide: opts.debug && this.renderGuide(item)
}));
guides.forEach(item => this.guides.push(item));
return guides;
},
getTranslate(transform, axis = 'x') {
let result = 0;
(transform || '').split(' ').forEach(item => {
@ -117,11 +310,95 @@ module.exports = {
}
},
onDrag() {
const { guidesTarget, opts } = this;
this.updateGuides(guidesTarget);
opts.debug && guidesTarget.forEach(item => this.renderGuide(item));
opts.guidesInfo &&
this.renderGuideInfo(guidesTarget.filter(item => item.active));
},
onEnd() {
const { editor, opts, id } = this;
const { onEnd } = opts;
onEnd && onEnd();
editor.stopCommand(id);
this.hideGuidesInfo();
},
hideGuidesInfo() {
['X', 'Y'].forEach(item => {
const guide = this[`elGuideInfo${item}`];
if (guide) guide.style.display = 'none';
});
},
/**
* Render guides with spacing information
*/
renderGuideInfo(guides = []) {
const { guidesStatic, editor } = this;
this.hideGuidesInfo();
guides.forEach(item => {
const { origin, x } = item;
const rectOrigin = editor.Canvas.getElementPos(origin);
const axis = isUndefined(x) ? 'y' : 'x';
const isY = axis === 'y';
const origEdge1 = rectOrigin[isY ? 'left' : 'top'];
const origEdge1Raw = rectOrigin.rect[isY ? 'left' : 'top'];
const origEdge2 = isY
? origEdge1 + rectOrigin.width
: origEdge1 + rectOrigin.height;
const origEdge2Raw = isY
? origEdge1Raw + rectOrigin.rect.width
: origEdge1Raw + rectOrigin.rect.height;
const elGuideInfo = this[`elGuideInfo${axis.toUpperCase()}`];
const elGuideInfoCnt = this[`elGuideInfoContent${axis.toUpperCase()}`];
const guideInfoStyle = elGuideInfo.style;
// Find the nearest element
const res = guidesStatic
.filter(stat => stat[axis] === item[axis])
.map(stat => {
const { left, width, top, height } = stat.originRect;
const statEdge1 = isY ? left : top;
const statEdge2 = isY ? left + width : top + height;
return {
gap:
statEdge2 < origEdge1
? origEdge1 - statEdge2
: statEdge1 - origEdge2,
guide: stat
};
})
.filter(item => item.gap > 0)
.sort((a, b) => a.gap - b.gap)
.map(item => item.guide)[0];
if (res) {
const { left, width, top, height, rect } = res.originRect;
const isEdge1 = isY ? left < rectOrigin.left : top < rectOrigin.top;
const statEdge1 = isY ? left : top;
const statEdge1Raw = isY ? rect.left : rect.top;
const statEdge2 = isY ? left + width : top + height;
const statEdge2Raw = isY
? rect.left + rect.width
: rect.top + rect.height;
const pos2 = `${isY ? item.y : item.x}px`;
const size = isEdge1 ? origEdge1 - statEdge2 : statEdge1 - origEdge2;
const sizeRaw = isEdge1
? origEdge1Raw - statEdge2Raw
: statEdge1Raw - origEdge2Raw;
guideInfoStyle.display = '';
guideInfoStyle[isY ? 'top' : 'left'] = pos2;
guideInfoStyle[isY ? 'left' : 'top'] = `${
isEdge1 ? statEdge2 : origEdge2
}px`;
guideInfoStyle[isY ? 'width' : 'height'] = `${size}px`;
elGuideInfoCnt.innerHTML = `${Math.round(sizeRaw)}px`;
}
});
},
toggleDrag(on) {

66
src/commands/view/Preview.js

@ -1,67 +1,65 @@
import _ from 'underscore';
import { each } from 'underscore';
module.exports = {
getPanels(editor) {
if (!this.panels) this.panels = editor.Panels.getPanelsEl();
if (!this.panels) {
this.panels = editor.Panels.getPanelsEl();
}
return this.panels;
},
tglPointers(editor, v) {
var elP = editor.Canvas.getBody().querySelectorAll(
'.' + this.ppfx + 'no-pointer'
);
_.each(elP, item => {
item.style.pointerEvents = v ? '' : 'all';
});
tglPointers(editor, val) {
const body = editor.Canvas.getBody();
const elP = body.querySelectorAll(`.${this.ppfx}no-pointer`);
each(elP, item => (item.style.pointerEvents = val ? '' : 'all'));
},
run(editor, sender) {
if (sender && sender.set) sender.set('active', false);
this.sender = sender;
editor.stopCommand('sw-visibility');
editor.getModel().stopDefault();
var that = this;
var panels = this.getPanels(editor);
var canvas = editor.Canvas.getElement();
var editorEl = editor.getEl();
var pfx = editor.Config.stylePrefix;
const panels = this.getPanels(editor);
const canvas = editor.Canvas.getElement();
const editorEl = editor.getEl();
const pfx = editor.Config.stylePrefix;
if (!this.helper) {
this.helper = document.createElement('span');
this.helper.className = pfx + 'off-prv fa fa-eye-slash';
editorEl.appendChild(this.helper);
this.helper.onclick = () => {
editor.stopCommand('preview');
};
const helper = document.createElement('span');
helper.className = `${pfx}off-prv fa fa-eye-slash`;
editorEl.appendChild(helper);
helper.onclick = () => editor.stopCommand('preview');
this.helper = helper;
}
this.helper.style.display = 'inline-block';
this.tglPointers(editor);
/*
editor.Canvas.getBody().querySelectorAll('.' + pfx + 'no-pointer').forEach(function(){
this.style.pointerEvents = 'all';
});*/
panels.style.display = 'none';
var canvasS = canvas.style;
const canvasS = canvas.style;
canvasS.width = '100%';
canvasS.height = '100%';
canvasS.top = '0';
canvasS.left = '0';
canvasS.padding = '0';
canvasS.margin = '0';
editor.trigger('change:canvasOffset');
editor.refresh();
},
stop(editor, sender) {
var panels = this.getPanels(editor);
stop(editor) {
const { sender = {} } = this;
sender.set && sender.set('active', 0);
const panels = this.getPanels(editor);
editor.runCommand('sw-visibility');
editor.getModel().runDefault();
panels.style.display = 'block';
var canvas = editor.Canvas.getElement();
panels.style.display = '';
const canvas = editor.Canvas.getElement();
canvas.setAttribute('style', '');
if (this.helper) {
this.helper.style.display = 'none';
}
editor.trigger('change:canvasOffset');
editor.refresh();
this.tglPointers(editor, 1);
}
};

4
src/dom_components/index.js

@ -498,7 +498,9 @@ module.exports = () => {
* @return {this}
*/
clear() {
this.getComponents().reset();
this.getComponents()
.map(i => i)
.forEach(i => i.remove());
return this;
},

1
src/dom_components/model/ComponentMap.js

@ -6,6 +6,7 @@ module.exports = Component.extend(
defaults: {
...Component.prototype.defaults,
type: 'map',
src: '',
void: 0,
mapUrl: 'https://maps.google.com/maps',
tagName: 'iframe',

9
src/dom_components/view/ComponentTextNodeView.js

@ -1 +1,8 @@
module.exports = require('backbone').View.extend({});
module.exports = require('backbone').View.extend({
initialize() {
this.model.view = this;
},
_createElement() {
return document.createTextNode(this.model.get('content'));
}
});

64
src/dom_components/view/ComponentsView.js

@ -11,22 +11,39 @@ module.exports = Backbone.View.extend({
this.listenTo(coll, 'remove', this.removeChildren);
},
removeChildren(removed) {
removeChildren(removed, coll, opts = {}) {
const em = this.config.em;
const view = removed.view;
const temp = removed.opt.temporary;
const tempComp = removed.opt.temporary;
const tempRemove = opts.temporary;
if (!view) return;
view.remove.apply(view);
const children = view.childrenView;
children && children.stopListening();
removed.components().forEach(this.removeChildren.bind(this));
!temp && removed.removed();
if (em) {
removed.get('style-signature') &&
em
.get('Commands')
.run('core:component-style-clear', { target: removed });
!temp && em.trigger('component:remove', removed);
removed.components().forEach(it => this.removeChildren(it, coll, opts));
if (em && !tempRemove) {
// Remove the component from the global list
const id = removed.getId();
const domc = em.get('DomComponents');
delete domc.componentsById[id];
// Remove all related CSS rules
const allRules = em.get('CssComposer').getAll();
allRules.remove(
allRules.filter(
rule => rule.getSelectors().getFullString() === `#${id}`
)
);
if (!tempComp) {
const cm = em.get('Commands');
const hasSign = removed.get('style-signature');
const optStyle = { target: removed };
hasSign && cm.run('core:component-style-clear', optStyle);
removed.removed();
em.trigger('component:remove', removed);
}
}
},
@ -58,30 +75,25 @@ module.exports = Backbone.View.extend({
* */
addToCollection(model, fragmentEl, index) {
if (!this.compView) this.compView = require('./ComponentView');
var fragment = fragmentEl || null,
viewObject = this.compView;
var dt = this.opts.componentTypes;
var type = model.get('type');
for (var it = 0; it < dt.length; it++) {
var dtId = dt[it].id;
if (dtId == type) {
const { config, opts } = this;
const fragment = fragmentEl || null;
const dt = opts.componentTypes;
const type = model.get('type');
let viewObject = this.compView;
for (let it = 0; it < dt.length; it++) {
if (dt[it].id == type) {
viewObject = dt[it].view;
break;
}
}
//viewObject = dt[type] ? dt[type].view : dt.default.view;
var view = new viewObject({
const view = new viewObject({
model,
config: this.config,
config,
componentTypes: dt
});
var rendered = view.render().el;
if (view.model.get('type') == 'textnode')
rendered = document.createTextNode(view.model.get('content'));
let rendered = view.render().el;
if (fragment) {
fragment.appendChild(rendered);

2
src/editor/index.js

@ -87,6 +87,8 @@
* * `run:{commandName}:before` - Triggered before the command is called
* * `stop:{commandName}:before` - Triggered before the command is called to stop
* * `abort:{commandName}` - Triggered when the command execution is aborted (`editor.on(`run:preview:before`, opts => opts.abort = 1);`)
* * `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback
* * `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback
* ### General
* * `canvasScroll` - Canvas is scrolled
* * `update` - The structure of the template is updated (its HTML/CSS)

2
src/editor/model/Editor.js

@ -15,8 +15,8 @@ const deps = [
require('storage_manager'),
require('device_manager'),
require('parser'),
require('style_manager'),
require('selector_manager'),
require('style_manager'),
require('modal_dialog'),
require('code_manager'),
require('panels'),

39
src/modal_dialog/index.js

@ -21,6 +21,8 @@
* * [getTitle](#gettitle)
* * [setContent](#setcontent)
* * [getContent](#getcontent)
* * [onceClose](#onceclose)
* * [onceOpen](#onceopen)
*
* @module Modal
*/
@ -32,6 +34,10 @@ module.exports = () => {
ModalView = require('./view/ModalView');
var model, modal;
const triggerEvent = (enable, em) => {
em && em.trigger(`modal:${enable ? 'open' : 'close'}`);
};
return {
/**
* Name of the module
@ -55,11 +61,13 @@ module.exports = () => {
...config
};
this.em = c.em;
const em = c.em;
this.em = em;
var ppfx = c.pStylePrefix;
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
model = new ModalM(c);
model.on('change:open', (m, enb) => triggerEvent(enb, em));
modal = new ModalView({
model,
config: c
@ -73,11 +81,6 @@ module.exports = () => {
this.render().appendTo(el);
},
triggerEvent(event) {
const { em } = this;
em && em.trigger(`modal:${event}`);
},
/**
* Open the modal window
* @param {Object} [opts={}] Options
@ -89,7 +92,6 @@ module.exports = () => {
opts.title && this.setTitle(opts.title);
opts.content && this.setContent(opts.content);
modal.show();
this.triggerEvent('open');
return this;
},
@ -99,7 +101,28 @@ module.exports = () => {
*/
close() {
modal.hide();
this.triggerEvent('close');
return this;
},
/**
* Execute callback when the modal will be closed.
* The callback will be called one only time
* @param {Function} clb
* @returns {this}
*/
onceClose(clb) {
this.em.once('modal:close', clb);
return this;
},
/**
* Execute callback when the modal will be opened.
* The callback will be called one only time
* @param {Function} clb
* @returns {this}
*/
onceOpen(clb) {
this.em.once('modal:open', clb);
return this;
},

4
src/panels/config/config.js

@ -59,24 +59,28 @@ module.exports = {
className: 'fa fa-paint-brush',
command: osm,
active: true,
togglable: 0,
attributes: { title: 'Open Style Manager' }
},
{
id: otm,
className: 'fa fa-cog',
command: otm,
togglable: 0,
attributes: { title: 'Settings' }
},
{
id: ola,
className: 'fa fa-bars',
command: ola,
togglable: 0,
attributes: { title: 'Open Layer Manager' }
},
{
id: obl,
className: 'fa fa-th-large',
command: obl,
togglable: 0,
attributes: { title: 'Open Blocks' }
}
]

2
src/panels/index.js

@ -216,7 +216,7 @@ module.exports = () => {
active() {
this.getPanels().each(p => {
p.get('buttons').each(btn => {
if (btn.get('active')) btn.trigger('updateActive');
btn.get('active') && btn.trigger('updateActive');
});
});
},

15
src/panels/model/Buttons.js

@ -27,13 +27,12 @@ module.exports = Backbone.Collection.extend({
*
* @return void
* */
deactivateAll(ctx) {
var context = ctx || '';
this.forEach((model, index) => {
if (model.get('context') == context) {
model.set('active', false);
if (model.get('buttons').length)
model.get('buttons').deactivateAll(context);
deactivateAll(ctx, sender) {
const context = ctx || '';
this.forEach(model => {
if (model.get('context') == context && model !== sender) {
model.set('active', false, { silent: 1 });
model.trigger('updateActive', { fromCollection: 1 });
}
});
},
@ -49,8 +48,6 @@ module.exports = Backbone.Collection.extend({
this.forEach((model, index) => {
if (model.get('context') == context) {
model.set('disable', true);
if (model.get('buttons').length)
model.get('buttons').disableAllButtons(context);
}
});
},

18
src/panels/view/ButtonView.js

@ -75,34 +75,32 @@ module.exports = Backbone.View.extend({
*
* @return void
* */
updateActive() {
const { model, commands, em } = this;
updateActive(opts = {}) {
const { model, commands, $el, activeCls } = this;
const { fromCollection } = opts;
const context = model.get('context');
const options = model.get('options');
const commandName = model.get('command');
let command = {};
var editor = em && em.get ? em.get('Editor') : null;
var commandName = model.get('command');
var cmdIsFunc = isFunction(commandName);
if (commands && isString(commandName)) {
command = commands.get(commandName) || {};
} else if (cmdIsFunc) {
} else if (isFunction(commandName)) {
command = commands.create({ run: commandName });
} else if (commandName !== null && isObject(commandName)) {
command = commands.create(commandName);
}
if (model.get('active')) {
model.collection.deactivateAll(context);
!fromCollection && model.collection.deactivateAll(context, model);
model.set('active', true, { silent: true }).trigger('checkActive');
commands.runCommand(command, { ...options, sender: model });
// Disable button if the command has no stop method
command.noStop && model.set('active', false);
} else {
this.$el.removeClass(this.activeCls);
model.collection.deactivateAll(context);
commands.stopCommand(command, { ...options, sender: model });
$el.removeClass(activeCls);
commands.stopCommand(command, { ...options, sender: model, force: 1 });
}
},

10
src/storage_manager/config/config.js

@ -55,5 +55,13 @@ module.exports = {
// false: 'x-www-form-urlencoded'
contentTypeJson: true,
credentials: 'include'
credentials: 'include',
// Pass custom options to fetch API (remote storage)
// You can pass a simple object: { someOption: 'someValue' }
// or a function wich returns and object to add:
// currentOpts => {
// return currentOpts.method === 'post' ? { method: 'patch' } : {};
// }
fetchOptions: ''
};

15
src/storage_manager/model/RemoteStorage.js

@ -1,5 +1,5 @@
import fetch from 'utils/fetch';
import { isUndefined } from 'underscore';
import { isUndefined, isFunction } from 'underscore';
module.exports = require('backbone').Model.extend({
fetch,
@ -11,7 +11,8 @@ module.exports = require('backbone').Model.extend({
beforeSend() {},
onComplete() {},
contentTypeJson: false,
credentials: 'include'
credentials: 'include',
fetchOptions: ''
},
/**
@ -123,8 +124,16 @@ module.exports = require('backbone').Model.extend({
fetchOptions.body = body;
}
const fetchOpts = this.get('fetchOptions') || {};
const addOpts = isFunction(fetchOpts)
? fetchOpts(fetchOptions)
: fetchOptions;
this.onStart();
this.fetch(url, fetchOptions)
this.fetch(url, {
...fetchOptions,
...(addOpts || {})
})
.then(res =>
((res.status / 200) | 0) == 1
? res.text()

2
src/style_manager/model/Sector.js

@ -72,7 +72,7 @@ module.exports = Backbone.Model.extend({
}
}
return ex ? isolated : props;
return ex ? isolated.filter(i => i) : props;
},
/**

90
src/styles/scss/_gjs_canvas.scss

@ -1,5 +1,94 @@
$frameAnimation: 0.35s ease;
$canvasTop: 40px;
$guide_pad: 5px;
.#{$prefix} {
&guide-info {
position: absolute;
&__content {
position: absolute;
height: 100%;
display: flex;
width: 100%;
padding: 5px;
}
&__line {
position: relative;
margin: auto;
&::before,
&::after {
content: "";
display: block;
position: absolute;
background-color: inherit;
}
}
&__y {
padding: 0 $guide_pad;
.#{$prefix}guide-info {
&__content {
justify-content: center;
}
&__line {
width: 100%;
height: 1px;
&::before,
&::after {
width: 1px;
height: 10px;
top: 0;
bottom: 0;
left: 0;
margin: auto;
}
&::after {
left: auto;
right: 0;
}
}
}
}
&__x {
padding: $guide_pad 0;
.#{$prefix}guide-info {
&__content {
align-items: center;
}
&__line {
height: 100%;
width: 1px;
&::before,
&::after {
width: 10px;
height: 1px;
left: 0;
right: 0;
top: 0;
margin: auto;
transform: translateX(-50%);
}
&::after {
top: auto;
bottom: 0;
}
}
}
}
}
}
.#{$cv-prefix}canvas {
background-color: rgba(0, 0, 0, 0.15);
@ -57,6 +146,7 @@ $canvasTop: 40px;
##{$app-prefix}tools {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;

8
src/styles/scss/main.scss

@ -52,8 +52,14 @@ $prefix: $app-prefix;
@import "gjs_status";
$colorsAll: (one, $primaryColor),
(two, $secondaryColor),
(three, $tertiaryColor),
(four, $quaternaryColor),
(danger, $colorRed);
.#{$prefix} {
@each $cnum, $ccol in (one, $primaryColor), (two, $secondaryColor), (three, $tertiaryColor), (four, $quaternaryColor) {
@each $cnum, $ccol in $colorsAll {
&#{$cnum} {
&-bg {
background-color: $ccol;

113
src/utils/Dragger.js

@ -1,4 +1,4 @@
import { bindAll, isFunction, result } from 'underscore';
import { bindAll, isFunction, result, isUndefined } from 'underscore';
import { on, off } from 'utils/mixins';
export default class Dragger {
@ -42,6 +42,15 @@ export default class Dragger {
*/
getPosition: null,
// Static guides to be snapped
guidesStatic: null,
// Target guides that will snap to static one
guidesTarget: null,
// Offset before snap to guides
snapOffset: 5,
// Document on which listen to pointer events
doc: 0,
@ -78,9 +87,12 @@ export default class Dragger {
* @param {Event} e
*/
start(ev) {
const { onStart } = this.opts;
const { opts } = this;
const { onStart } = opts;
this.toggleDrag(1);
this.startPointer = this.getPointerPos(ev);
this.guidesStatic = result(opts, 'guidesStatic') || [];
this.guidesTarget = result(opts, 'guidesTarget') || [];
isFunction(onStart) && onStart(ev, this);
this.startPosition = this.getStartPosition();
this.drag(ev);
@ -114,17 +126,104 @@ export default class Dragger {
delta.y = startPointer.y;
}
['x', 'y'].forEach(co => (delta[co] = delta[co] * result(opts, 'scale')));
this.lockedAxis = lockedAxis;
this.delta = delta;
this.move(delta.x, delta.y);
const moveDelta = delta => {
['x', 'y'].forEach(co => (delta[co] = delta[co] * result(opts, 'scale')));
this.delta = delta;
this.move(delta.x, delta.y);
isFunction(onDrag) && onDrag(ev, this);
};
const deltaPre = { ...delta };
this.currentPointer = currentPos;
isFunction(onDrag) && onDrag(ev, this);
this.lockedAxis = lockedAxis;
moveDelta(delta);
if (this.guidesTarget.length) {
const { newDelta, trgX, trgY } = this.snapGuides(deltaPre);
(trgX || trgY) && moveDelta(newDelta);
}
// In case the mouse button was released outside of the window
ev.which === 0 && this.stop(ev);
}
/**
* Check if the delta hits some guide
*/
snapGuides(delta) {
const newDelta = delta;
let { trgX, trgY } = this;
this.guidesTarget.forEach(trg => {
// Skip the guide if its locked axis already exists
if ((trg.x && this.trgX) || (trg.y && this.trgY)) return;
trg.active = 0;
this.guidesStatic.forEach(stat => {
if ((trg.y && stat.x) || (trg.x && stat.y)) return;
const isY = trg.y && stat.y;
const axs = isY ? 'y' : 'x';
const trgPoint = trg[axs];
const statPoint = stat[axs];
const deltaPoint = delta[axs];
const trgGuide = isY ? trgY : trgX;
if (this.isPointIn(trgPoint, statPoint)) {
if (isUndefined(trgGuide)) {
const trgValue = deltaPoint - (trgPoint - statPoint);
this.setGuideLock(trg, trgValue);
}
}
});
});
trgX = this.trgX;
trgY = this.trgY;
['x', 'y'].forEach(co => {
const axis = co.toUpperCase();
let trg = this[`trg${axis}`];
if (trg && !this.isPointIn(delta[co], trg.lock)) {
this.setGuideLock(trg, null);
trg = null;
}
if (trg && !isUndefined(trg.lock)) {
newDelta[co] = trg.lock;
}
});
return {
newDelta,
trgX: this.trgX,
trgY: this.trgY
};
}
isPointIn(src, trg, { offset } = {}) {
const ofst = offset || this.opts.snapOffset;
return (
(src >= trg && src <= trg + ofst) || (src <= trg && src >= trg - ofst)
);
}
setGuideLock(guide, value) {
const axis = !isUndefined(guide.x) ? 'X' : 'Y';
const trgName = `trg${axis}`;
if (value !== null) {
guide.active = 1;
guide.lock = value;
this[trgName] = guide;
} else {
delete guide.active;
delete guide.lock;
delete this[trgName];
}
return guide;
}
/**
* Stop dragging
*/

7
src/utils/Sorter.js

@ -320,10 +320,9 @@ module.exports = Backbone.View.extend({
const opts = {
avoidStore: 1,
avoidChildren: 1,
avoidUpdateStyle: 1,
temporary: 1
avoidUpdateStyle: 1
};
let tempModel = comps.add(dropContent, opts);
let tempModel = comps.add(dropContent, { ...opts, temporary: 1 });
dropModel = comps.remove(tempModel, opts);
this.dropModel = dropModel instanceof Array ? dropModel[0] : dropModel;
}
@ -995,7 +994,7 @@ module.exports = Backbone.View.extend({
modelTemp = targetCollection.add({}, { ...opts });
if (model) {
modelToDrop = model.collection.remove(model);
modelToDrop = model.collection.remove(model, { temporary: 1 });
}
} else {
modelToDrop = dropContent;

17
test/specs/commands/view/CommandAbstract.js

@ -24,8 +24,7 @@ module.exports = {
const runStub = sinon.stub(command, 'run').returns('result');
const result = command.callRun(editor);
expect(editorTriggerSpy.calledTwice).toEqual(true);
expect(editorTriggerSpy.callCount).toEqual(3);
expect(editorTriggerSpy.getCall(0).args).toEqual([
'run:test:before',
{}
@ -35,6 +34,12 @@ module.exports = {
'result',
{}
]);
expect(editorTriggerSpy.getCall(2).args).toEqual([
'run',
'test',
'result',
{}
]);
expect(result).toEqual('result');
expect(runStub.calledOnce).toEqual(true);
@ -64,7 +69,7 @@ module.exports = {
const result = command.callStop(editor);
expect(editorTriggerSpy.calledTwice).toEqual(true);
expect(editorTriggerSpy.callCount).toEqual(3);
expect(editorTriggerSpy.getCall(0).args).toEqual([
'stop:test:before',
{}
@ -74,6 +79,12 @@ module.exports = {
'stopped',
{}
]);
expect(editorTriggerSpy.getCall(2).args).toEqual([
'stop',
'test',
'stopped',
{}
]);
expect(result).toEqual('stopped');
expect(stopStub.calledOnce).toEqual(true);

17
test/specs/storage_manager/model/Models.js

@ -141,6 +141,23 @@ module.exports = {
credentials: false
});
});
test('Load data with custom fetch options as function', () => {
const customOpts = { customOpt: 'customValue' };
obj = new RemoteStorage({
...storageOptions,
fetchOptions: () => {
return customOpts;
}
});
sinon
.stub(obj, 'fetch')
.returns(Promise.resolve(mockResponse({ data: 1 })));
obj.load(['item1', 'item2']);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject(customOpts);
});
});
}
};

Loading…
Cancel
Save