Browse Source

Update Layers doc

pull/4487/head
Artur Arseniev 4 years ago
parent
commit
fbd14cc9ec
  1. 317
      docs/modules/Layers.md

317
docs/modules/Layers.md

@ -17,7 +17,7 @@ This guide is referring to GrapesJS v0.19.4 or higher
## Configuration
To change the default configurations you have to pass the `layerManager` property with the main configuration object.
To change the default configurations you have to pass the `layerManager` option with the main configuration object.
```js
const editor = grapesjs.init({
@ -28,6 +28,9 @@ const editor = grapesjs.init({
});
```
You can check here the full list of available configuration options: [Layer Manager Config](https://github.com/artf/grapesjs/blob/master/src/navigator/config/config.ts)
Layers are a direct representation of your components, therefore they will only be available once your components are loaded in the editor (eg. you might load your project data from a remote endpoint).
In your configuration, you're able to change the global behavior of layers (eg. make all the layers not sortable) and also decide which component layer should be used as a root.
@ -47,8 +50,6 @@ const editor = grapesjs.init({
The configurations are mainly targeting the default UI provided by GrapesJS core, in case you need more control over the tree of your layers, you can read more in the [Customization](#customization) section below.
You can check here the full list of available configuration options: [Layer Manager Config](https://github.com/artf/grapesjs/blob/master/src/navigator/config/config.ts)
## Programmatic usage
@ -61,32 +62,319 @@ If you need to manage layers programmatically you can use its [APIs][Layers API]
## Customization
The default UI can handle most of the common tasks but in case you need a more advanced logic/elements, that requires a replace of the default UI.
By using the [Layers API][Layers API] you're able to replace the default UI with your own implementation.
All you have to do is to indicate the editor your intent to use a custom UI and then subscribe to the `selector:custom` event that will trigger on any necessary update of the UI.
All you have to do is to indicate to the editor your intent to use a custom UI and then subscribe to a few events that allow you to properly update your UI.
```js
const editor = grapesjs.init({
// ...
selectorManager: {
layerManager: {
custom: true,
// ...
},
});
editor.on('selector:custom', props => {
// Use this event to append your UI in the default container provided by GrapesJS.
// You can skip this event if you don't rely on the core panels and decide to
// place the UI in some other place.
editor.on('layer:custom', (props) => {
// props.container (HTMLElement) - The default element where you can append your UI
// Here you would put the logic to render/update your UI.
});
```
In the example below we'll replicate most of the default functionality by using solely the Selector Manager API.
<demo-viewer value="v8cgkLfr" height="500" darkcode/>
// Triggered when the root layer is changed.
editor.on('layer:root', (root) => {
// Update the root of your UI
});
// Triggered when a component is updated, this allows you to update specific layers.
editor.on('layer:component', (component) => {
// Update the specific layer of your UI
});
```
In the example below we'll replicate most of the default functionality with our own implementation.
<demo-viewer value="L24hkgm5" height="500" darkcode/>
<!-- Demo template, here for reference
<style>
.layer-manager {
position: relative;
text-align: left;
}
.layer-item.hidden {
opacity: 0.5;
}
.layer-item-icon {
width: 15px;
cursor: pointer;
}
.layer-item-eye {
}
.layer-item-chevron {
transform: rotate(90deg);
}
.layer-item-chevron.open {
transform: rotate(180deg);
}
.layer-item-chevron.hidden {
opacity: 0;
pointer-events: none;
}
.layer-item-row {
display: flex;
align-items: center;
user-select: none;
gap: 8px;
padding: 5px 8px;
border-bottom: 1px solid rgba(0,0,0,0.35);
}
.layer-item-row.selected {
background-color: rgba(255,255,255,0.15);
}
.layer-item-row.hovered {
background-color: rgba(255,255,255,0.05);
}
.layer-item-name {
margin-left: 3px;
}
.layer-item-name.editing {
background-color: white;
color: #555;
padding: 0 3px;
}
.layer-item-name-cnt {
display: flex;
align-items: center;
flex-grow: 1;
}
.layer-drag-indicator {
position: absolute;
width: 100%;
height: 1px;
left: 0;
background-color: #3b97e3;
}
</style>
<div style="display: none">
<div
class="layer-manager"
@pointerdown="onDragStart"
@pointermove="onDragMove"
@pointerup="onDragEnd"
>
<layer-item v-if="root" :component="root" :level="0"></layer-item>
<div
v-if="dragIndicator.show"
class="layer-drag-indicator"
:style="{ top: `${dragIndicator.y}px`, marginLeft: `${dragIndicator.offset}px`, width: `calc(100% - ${dragIndicator.offset}px)` }"></div>
</div>
<div id="layer-item-template" style="display: none;">
<div :class="['layer-item', !visible && 'hidden']">
<div
:class="['layer-item-row', selected && 'selected', hovered && 'hovered']"
@click="setSelected"
@mouseenter="setHover(true)"
@mouseleave="setHover(false)"
ref="layerRef"
data-layer-item
>
<div class="layer-item-icon layer-item-eye" @click.stop="toggleVisibility()">
<svg v-if="visible" viewBox="0 0 24 24"><path fill="currentColor" d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" /></svg>
<svg v-else viewBox="0 0 24 24"><path fill="currentColor" d="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z" /></svg>
</div>
<div class="layer-item-name-cnt" :style="{ marginLeft: `${level*10}px` }">
<div :class="['layer-item-icon layer-item-chevron', open && 'open', !components.length && 'hidden']" @click.stop="toggleOpen()">
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z" /></svg>
</div>
<div ref="nameInput"
:class="['layer-item-name', editing && 'editing']"
:contenteditable="editing"
@dblclick.stop="setEditing(true)"
@blur.stop="setEditing(false)"
@keydown.enter="setEditing(false)"
>
{{ name }}
</div>
</div>
<div v-if="component.get('draggable')" class="layer-item-icon layer-item-move" data-layer-move>
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M13,6V11H18V7.75L22.25,12L18,16.25V13H13V18H16.25L12,22.25L7.75,18H11V13H6V16.25L1.75,12L6,7.75V11H11V6H7.75L12,1.75L16.25,6H13Z"/></svg>
</div>
</div>
<div v-if="open" class="layer-items">
<layer-item v-for="cmp in components" :key="cmp.getId()" :component="cmp" :level="level + 1"/>
</div>
</div>
</div>
</div>
<script>
const { Components, Layers } = editor;
const cmpElMap = new WeakMap();
Vue.component('layer-item', {
template: '#layer-item-template',
props: { component: Object, level: Number },
data() {
return {
name: '',
components: [],
visible: true,
open: false,
selected: false,
hovered: false,
editing: false,
}
},
mounted() {
this.updateLayer(Layers.getLayerData(this.component));
cmpElMap.set(this.$refs.layerRef, this.component);
editor.on('layer:component', this.onLayerComponentUpdate);
},
destroyed() {
editor.off('layer:component', this.onLayerComponentUpdate);
},
methods: {
onLayerComponentUpdate(cmp) {
if (cmp === this.component) {
this.updateLayer(Layers.getLayerData(cmp));
}
},
updateLayer(data) {
this.name = data.name;
this.components = data.components;
this.visible = data.visible;
this.open = data.open;
this.selected = data.selected;
this.hovered = data.hovered;
},
toggleVisibility() {
const { component } = this;
Layers.setVisible(this.component, !this.visible);
},
toggleOpen() {
const { component } = this;
Layers.setOpen(this.component, !this.open);
},
setHover(hovered) {
Layers.setLayerData(this.component, { hovered })
},
setSelected(event) {
Layers.setLayerData(this.component, { selected: true }, { event })
},
setEditing(value) {
this.editing = value;
const el = this.$refs.nameInput;
if (!value) {
Layers.setName(this.component, el.innerText)
} else {
setTimeout(() => el.focus())
}
},
}
});
const app = new Vue({
el: '.layer-manager',
data: {
root: null,
isDragging: false,
draggingCmp: null,
draggingOverCmp: null,
dragIndicator: {},
canMoveRes: {},
},
mounted() {
editor.on('layer:custom', this.handleCustom);
editor.on('layer:root', this.handleRootChange);
},
destroyed() {
editor.off('layer:custom', this.handleCustom);
editor.off('layer:root', this.handleRootChange);
},
methods: {
handleCustom(props = {}) {
const { container, root } = props;
container && container.appendChild(this.$el);
this.handleRootChange(root);
},
handleRootChange(root) {
console.log('root update', root);
this.root = root;
},
getDragTarget(ev) {
const el = document.elementFromPoint(ev.clientX, ev.clientY);
const dragEl = el?.closest('[data-layer-move]');
const elLayer = el?.closest('[data-layer-item]');
return {
dragEl,
elLayer,
cmp: cmpElMap.get(elLayer),
}
},
onDragStart(ev) {
if (this.getDragTarget(ev).dragEl) {
this.isDragging = true;
}
},
onDragMove(ev) {
if (!this.isDragging) return;
const { cmp, elLayer } = this.getDragTarget(ev);
if (!cmp || !elLayer) return;
const { draggingCmp } = this;
const layerRect = elLayer.getBoundingClientRect();
const layerH = elLayer.offsetHeight;
const layerY = elLayer.offsetTop;
const pointerY = ev.clientY;
const isBefore = pointerY < (layerRect.y + layerH / 2);
const cmpSource = !draggingCmp ? cmp : draggingCmp;
const cmpTarget = cmp.parent();
const cmpIndex = cmp.index() + (isBefore ? 0 : 1);
this.draggingCmp = !draggingCmp ? cmp : draggingCmp;
this.draggingOverCmp = cmp;
const canMove = Components.canMove(cmpTarget, cmpSource, cmpIndex);
const canMoveInside = Components.canMove(cmp, cmpSource);
const canMoveRes = {
...canMove,
canMoveInside,
index: cmpIndex,
};
// if (
// canMoveInside.result &&
// (
// pointerY > (layerRect.y + LAYER_PAD)
// && pointerY < (layerRect.y + layerH - LAYER_PAD))
// ) {
// pointerInside = true;
// canMoveRes.target = cmp;
// delete canMoveRes.index;
// }
// setDragParent(pointerInside ? cmp : undefined);
this.canMoveRes = canMoveRes;
const dragLevel = (cmp ? cmp.parents() : []).length;
this.dragIndicator = {
y: layerY + (isBefore ? 0 : layerH),
h: layerH,
offset: dragLevel * 10 + 20,
show: !!(this.draggingCmp && canMoveRes?.result),
};
},
onDragEnd(ev) {
const { canMoveRes } = this;
canMoveRes.result && canMoveRes.source.move(canMoveRes.target, { at: canMoveRes.index });
this.isDragging = false;
this.draggingCmp = null;
this.draggingOverCmp = null;
this.dragIndicator = {};
this.canMoveRes = {};
},
}
});
</script>
-->
## Events
@ -94,8 +382,5 @@ In the example below we'll replicate most of the default functionality by using
For a complete list of available events, you can check it [here](/api/layer_manager.html#available-events).
[Selector]: </api/selector.html>
[Style Manager]: <Style-manager.html>
[Components]: <Components.html>
[Getting Started]: </getting-started.html>
[Layers API]: </api/layer_manager.html>

Loading…
Cancel
Save