allow to move blocks
update builder view
This commit is contained in:
parent
7fe1acd47d
commit
78965bbf10
|
@ -773,10 +773,9 @@ label.required::after {
|
|||
.block-header-item {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
margin-bottom: 3px;
|
||||
margin-bottom: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -788,7 +787,7 @@ label.required::after {
|
|||
color: lighten(map-get($theme-colors, 'dark-blue'), 100%);
|
||||
}
|
||||
|
||||
.block-settings-toggler {
|
||||
.block-settings-inverse {
|
||||
background: none;
|
||||
border: 1px solid map-get($theme-colors, 'dark-blue');
|
||||
color: map-get($theme-colors, 'dark-blue');
|
||||
|
@ -798,9 +797,13 @@ label.required::after {
|
|||
padding: 4px;
|
||||
}
|
||||
|
||||
.block-id {
|
||||
font-size: 12px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.builder-add {
|
||||
text-align: center;
|
||||
margin-bottom: -5px;
|
||||
|
||||
.picker {
|
||||
text-align: left;
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
v-for="(block, key) in value"
|
||||
:item="block"
|
||||
:widgets="widgets"
|
||||
:isFirst="key === 0"
|
||||
:isLast="key == Object.keys(value)[Object.keys(value).length -1]"
|
||||
@remove-item="removeBlock(key)"
|
||||
@move-item-up="moveBlockUp(key)"
|
||||
@move-item-down="moveBlockDown(key)"
|
||||
/>
|
||||
<div class="container">
|
||||
<BuilderBlockCreate
|
||||
|
@ -20,18 +24,27 @@
|
|||
<script>
|
||||
import BuilderBlockItem from './BuilderBlockItem'
|
||||
import BuilderBlockCreate from './BuilderBlockCreate'
|
||||
import BuilderBlockForm from './BuilderBlockForm'
|
||||
|
||||
const widgets = {
|
||||
row: {
|
||||
label: 'Bootstrap row',
|
||||
bsContainer: {
|
||||
category: 'Boostrap',
|
||||
label: 'Container',
|
||||
settings: {
|
||||
},
|
||||
isContainer: true,
|
||||
widgets: ['column'],
|
||||
widgets: [],
|
||||
},
|
||||
column: {
|
||||
label: 'Bootstrap column',
|
||||
bsRow: {
|
||||
category: 'Boostrap',
|
||||
label: 'Row',
|
||||
settings: {
|
||||
},
|
||||
isContainer: true,
|
||||
widgets: ['bsColumn'],
|
||||
},
|
||||
bsColumn: {
|
||||
category: 'Boostrap',
|
||||
label: 'Column',
|
||||
settings: {
|
||||
size: {label: 'Size', type: 'input', attr: {type: 'number'}},
|
||||
sizeMd: {label: 'Size MD', type: 'input', attr: {type: 'number'}},
|
||||
|
@ -40,15 +53,102 @@ const widgets = {
|
|||
widgets: [],
|
||||
},
|
||||
tinymce: {
|
||||
category: 'Editor',
|
||||
label: 'TinyMCE',
|
||||
settings: {
|
||||
value: {label: 'Value', type: 'textarea', attr: {'data-tinymce': ''}},
|
||||
},
|
||||
isContainer: false,
|
||||
widgets: [],
|
||||
},
|
||||
select: {
|
||||
category: null,
|
||||
label: 'Item with Select',
|
||||
settings: {
|
||||
value: {label: 'Value', type: 'select', attr: {}, options: [
|
||||
{value: 'a', text: 'A'},
|
||||
{value: 'b', text: 'B'},
|
||||
]}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'BuilderBlock',
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialValue: {
|
||||
type: Array,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: this.initialValue,
|
||||
widgets,
|
||||
blockKey: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toJson(value) {
|
||||
return JSON.stringify(value)
|
||||
},
|
||||
triggerBuilderBlockEvent() {
|
||||
document.querySelector('body').dispatchEvent(new Event('builder_block.update'))
|
||||
},
|
||||
moveBlockUp(key) {
|
||||
const prev = this.value[key-1]
|
||||
const current = this.value[key]
|
||||
|
||||
this.value[key-1] = current
|
||||
this.value[key] = prev
|
||||
++this.blockKey
|
||||
},
|
||||
moveBlockDown(key) {
|
||||
const next = this.value[key+1]
|
||||
const current = this.value[key]
|
||||
|
||||
this.value[key+1] = current
|
||||
this.value[key] = next
|
||||
++this.blockKey
|
||||
},
|
||||
removeBlock(key) {
|
||||
let newValue = []
|
||||
|
||||
this.value.forEach((v, k) => {
|
||||
if (k !== key) {
|
||||
newValue.push(v)
|
||||
}
|
||||
})
|
||||
|
||||
this.value = newValue
|
||||
++this.blockKey
|
||||
},
|
||||
},
|
||||
components: {
|
||||
BuilderBlockItem,
|
||||
BuilderBlockCreate,
|
||||
},
|
||||
mounted() {
|
||||
this.triggerBuilderBlockEvent()
|
||||
},
|
||||
created() {
|
||||
this.triggerBuilderBlockEvent()
|
||||
},
|
||||
updated() {
|
||||
this.triggerBuilderBlockEvent()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
const blocks = [
|
||||
{
|
||||
widget: 'row',
|
||||
|
@ -81,65 +181,5 @@ const blocks = [
|
|||
]
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'BuilderBlock',
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialValue: {
|
||||
type: Array,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: this.initialValue,
|
||||
widgets,
|
||||
blockKey: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toJson(value) {
|
||||
return JSON.stringify(value)
|
||||
},
|
||||
triggerBuilderBlockEvent() {
|
||||
document.querySelector('body').dispatchEvent(new Event('builder_block.update'))
|
||||
},
|
||||
removeBlock(key) {
|
||||
let newValue = []
|
||||
|
||||
this.value.forEach((v, k) => {
|
||||
if (k !== key) {
|
||||
newValue.push(v)
|
||||
}
|
||||
})
|
||||
|
||||
console.log(newValue)
|
||||
|
||||
this.value = newValue
|
||||
++this.blockKey
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BuilderBlockItem,
|
||||
BuilderBlockCreate,
|
||||
BuilderBlockForm,
|
||||
},
|
||||
mounted() {
|
||||
this.triggerBuilderBlockEvent()
|
||||
},
|
||||
created() {
|
||||
this.triggerBuilderBlockEvent()
|
||||
},
|
||||
updated() {
|
||||
this.triggerBuilderBlockEvent()
|
||||
}
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
|
|
|
@ -1,18 +1,66 @@
|
|||
<style scoped>
|
||||
.categories {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.category {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.category-label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.widget {
|
||||
display: inline-block;
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
border: 1px solid #1e2430;
|
||||
}
|
||||
|
||||
.widget:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.widget-label {
|
||||
font-weight: bold;
|
||||
padding: 0 0 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="builder-add">
|
||||
<span class="btn btn-sm btn-secondary" v-on:click="togglePicker">
|
||||
<span class="fa fa-plus"></span>
|
||||
</span>
|
||||
|
||||
<div class="picker mt-2 list-group" :class="{'d-none': !showPicker}">
|
||||
<div v-for="(widget, name) in widgets" v-if="allowedWidgets.length == 0 || allowedWidgets.includes(name)" class="list-group-item">
|
||||
<div class="float-right">
|
||||
<span class="btn btn-sm btn-primary" v-on:click="add(name, widget)">
|
||||
<span class="fa fa-plus"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="categories mt-2 list-group" :class="{'d-none': !showPicker}">
|
||||
<div
|
||||
v-for="category in categories()"
|
||||
v-if="Object.keys(category.widgets).length"
|
||||
class="category"
|
||||
>
|
||||
<div
|
||||
v-if="category.label != 'none'"
|
||||
v-text="category.label"
|
||||
class="category-label row"
|
||||
></div>
|
||||
|
||||
{{ widget.label }}
|
||||
<div
|
||||
v-for="(widget, name) in category.widgets"
|
||||
v-on:click="add(name, widget)"
|
||||
class="widget col-3"
|
||||
>
|
||||
<div class="widget-label">
|
||||
{{ widget.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,12 +86,13 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
showPicker: false
|
||||
showPicker: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add(name, widget) {
|
||||
this.container.push({
|
||||
id: this.makeId(),
|
||||
widget: name,
|
||||
settings: {},
|
||||
children: [],
|
||||
|
@ -52,13 +101,44 @@ export default {
|
|||
this.$emit('updateContainer', this.container)
|
||||
this.togglePicker()
|
||||
},
|
||||
makeId() {
|
||||
let result = ''
|
||||
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
const charactersLength = characters.length
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength))
|
||||
}
|
||||
|
||||
return `block-${result}`
|
||||
},
|
||||
togglePicker() {
|
||||
this.showPicker = !this.showPicker
|
||||
},
|
||||
categories() {
|
||||
let items = {}
|
||||
|
||||
for (let widgetName in this.widgets) {
|
||||
let value = this.widgets[widgetName]
|
||||
|
||||
if (!value.category) {
|
||||
value.category = 'none'
|
||||
}
|
||||
|
||||
if (typeof items[value.category] === 'undefined') {
|
||||
items[value.category] = {
|
||||
label: value.category,
|
||||
widgets: {},
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.allowedWidgets.length || this.allowedWidgets.includes(widgetName)) {
|
||||
items[value.category].widgets[widgetName] = value
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
},
|
||||
components: {
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="value !== null && typeof value === 'object'">
|
||||
<BuilderBlockForm v-for="(item, key) in value" :name="name + '[' + key + ']'" :value="item" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<input type="text" :name="name" :value="value">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BuilderBlockForm',
|
||||
props: {
|
||||
name: {
|
||||
type: name,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -2,39 +2,51 @@
|
|||
<div class="block" v-if="widget" :key="blockKey">
|
||||
<div class="block-header">
|
||||
<div class="float-right">
|
||||
<span class="block-id">
|
||||
{{ item.id }}
|
||||
</span>
|
||||
<div class="block-header-item text-white bg-danger" v-on:click="removeMe(item)">
|
||||
<span class="fa fa-trash"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-header-item block-label" data-toggle="tooltip" data-placement="top" :title="item.widget">
|
||||
<div
|
||||
class="block-header-item block-label"
|
||||
:title="item.widget"
|
||||
>
|
||||
{{ widget.label }}
|
||||
</div>
|
||||
<div class="block-header-item block-settings-toggler" v-on:click="toggleSettings" v-if="Object.keys(widget.settings).length">
|
||||
<div
|
||||
class="block-header-item block-settings-inverse"
|
||||
v-on:click="toggleSettings"
|
||||
v-if="Object.keys(widget.settings).length"
|
||||
>
|
||||
<span class="fa fa-cog"></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!isFirst"
|
||||
v-on:click="moveMeUp"
|
||||
class="block-header-item block-settings-inverse"
|
||||
>
|
||||
<span class="fas fa-arrow-up"></span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isLast"
|
||||
v-on:click="moveMeDown"
|
||||
class="block-header-item block-settings-inverse"
|
||||
>
|
||||
<span class="fas fa-arrow-down"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-settings" v-if="Object.keys(widget.settings).length" :class="{'d-none': !showSettings}">
|
||||
<div class="row">
|
||||
<div
|
||||
<BuilderBlockSetting
|
||||
v-for="(params, setting) in widget.settings"
|
||||
class="col-12 form-group"
|
||||
>
|
||||
<label v-text="params.label"></label>
|
||||
|
||||
<input
|
||||
v-if="params.type == 'input'"
|
||||
v-model="item.settings[setting]"
|
||||
v-bind="params.attr"
|
||||
class="form-control"
|
||||
/>
|
||||
<textarea
|
||||
v-if="params.type == 'textarea'"
|
||||
v-model="item.settings[setting]"
|
||||
v-bind="params.attr"
|
||||
class="form-control"
|
||||
></textarea>
|
||||
</div>
|
||||
:item="item"
|
||||
:params="params"
|
||||
:setting="setting"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -42,7 +54,11 @@
|
|||
<BuilderBlockItem
|
||||
:item="child"
|
||||
:widgets="widgets"
|
||||
:isFirst="key === 0"
|
||||
:isLast="key == Object.keys(item.children)[Object.keys(item.children).length -1]"
|
||||
@remove-item="removeBlock(key)"
|
||||
@move-item-up="moveBlockUp(key)"
|
||||
@move-item-down="moveBlockDown(key)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -58,6 +74,7 @@
|
|||
|
||||
<script>
|
||||
import BuilderBlockCreate from './BuilderBlockCreate'
|
||||
import BuilderBlockSetting from './BuilderBlockSetting'
|
||||
|
||||
export default {
|
||||
name: 'BuilderBlockItem',
|
||||
|
@ -69,6 +86,14 @@ export default {
|
|||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isFirst: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isLast: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -85,6 +110,28 @@ export default {
|
|||
removeMe() {
|
||||
this.$emit('remove-item')
|
||||
},
|
||||
moveMeUp() {
|
||||
this.$emit('move-item-up')
|
||||
},
|
||||
moveMeDown() {
|
||||
this.$emit('move-item-down')
|
||||
},
|
||||
moveBlockUp(key) {
|
||||
const prev = this.item.children[key-1]
|
||||
const current = this.item.children[key]
|
||||
|
||||
this.item.children[key-1] = current
|
||||
this.item.children[key] = prev
|
||||
++this.blockKey
|
||||
},
|
||||
moveBlockDown(key) {
|
||||
const next = this.item.children[key+1]
|
||||
const current = this.item.children[key]
|
||||
|
||||
this.item.children[key+1] = current
|
||||
this.item.children[key] = next
|
||||
++this.blockKey
|
||||
},
|
||||
removeBlock(key) {
|
||||
let children = []
|
||||
|
||||
|
@ -100,6 +147,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
BuilderBlockCreate,
|
||||
BuilderBlockSetting,
|
||||
},
|
||||
mounted() {
|
||||
this.widget = this.widgets[this.item.widget]
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div class="col-12 form-group">
|
||||
<label
|
||||
v-if="params.label"
|
||||
v-text="params.label">
|
||||
</label>
|
||||
|
||||
<input
|
||||
v-if="params.type == 'input'"
|
||||
v-model="item.settings[setting]"
|
||||
v-bind="params.attr"
|
||||
class="form-control"
|
||||
/>
|
||||
|
||||
<textarea
|
||||
v-if="params.type == 'textarea'"
|
||||
v-model="item.settings[setting]"
|
||||
v-bind="params.attr"
|
||||
class="form-control"
|
||||
></textarea>
|
||||
|
||||
<select
|
||||
v-if="params.type == 'select'"
|
||||
v-model="item.settings[setting]"
|
||||
v-bind="params.attr"
|
||||
class="form-control"
|
||||
>
|
||||
<option :value="v.value" v-for="(v, k) in params.options" :key="k">
|
||||
{{ v.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BuilderBlockSetting',
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
setting: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
Loading…
Reference in a new issue