allow to move blocks

update builder view
This commit is contained in:
Simon Vieille 2024-05-12 14:30:28 +02:00
parent 7fe1acd47d
commit 78965bbf10
Signed by: deblan
GPG key ID: 579388D585F70417
6 changed files with 332 additions and 133 deletions

View file

@ -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;

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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]

View file

@ -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>