builder block: allow to use the code editor on each item

This commit is contained in:
Simon Vieille 2024-06-02 21:12:32 +02:00
parent bc148f0b6b
commit 8e947b0b77
Signed by: deblan
GPG key ID: 579388D585F70417
4 changed files with 211 additions and 142 deletions

View file

@ -862,9 +862,16 @@ label.required::after {
overflow: hidden;
}
dialog {
.builder-code-editor {
border: 0;
padding: 0;
background: none;
position: fixed;
top: 15px;
textarea {
font-family: Monospace;
min-height: 50vh;
}
}
}

View file

@ -45,64 +45,23 @@
position="bottom"
/>
<div>
<button
type="button"
class="btn btn-sm"
v-on:click="openCodeEditor"
>
<i class="fas fa-code"></i>
</button>
<BuilderBlockCodeEditor
ref="dialog"
:value="value"
:widgets="widgets"
@update="codeUpdate"
/>
</div>
</div>
</div>
<textarea :name="name" class="d-none">{{ toJson(value) }}</textarea>
<dialog ref="dialog" class="modal-dialog modal-dialog-large">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Code editor</h5>
<button
type="button"
class="close"
v-on:click="closeCodeEditor"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<textarea
class="form-control"
rows="10"
ref="codeEditor"
v-model="nextValue"
>
</textarea>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
v-on:click="closeCodeEditor"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
v-on:click="checkAndSaveNextValue"
>
Save
</button>
</div>
</div>
</dialog>
</Draggable>
</div>
</template>
<script>
import BuilderBlockItem from './BuilderBlockItem'
import BuilderBlockCodeEditor from './BuilderBlockCodeEditor'
import BuilderBlockCreate from './BuilderBlockCreate'
import Routing from '../../../../../../../../../friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js'
import Draggable from 'vuedraggable'
@ -113,6 +72,12 @@ Routing.setRoutingData(routes)
export default {
name: 'BuilderBlock',
components: {
BuilderBlockItem,
BuilderBlockCreate,
Draggable,
BuilderBlockCodeEditor,
},
props: {
id: {
type: String,
@ -144,71 +109,8 @@ export default {
triggerBuilderBlockEvent() {
document.querySelector('body').dispatchEvent(new Event('builder_block.update'))
},
openCodeEditor() {
this.nextValue = this.toJson(this.value)
this.$refs.dialog.showModal()
},
closeCodeEditor() {
this.$refs.codeEditor.classList.toggle('is-invalid', false)
this.$refs.dialog.close()
},
isNextValueItemValueValid(item) {
const hasId = typeof item.id === 'string'
const hasWidget = typeof item.widget === 'string' && this.widgets[item.widget]
const hasSettings = typeof item.settings === 'object'
const hasChildren = Array.isArray(item.children)
if (!hasId || !hasWidget || !hasSettings || !hasChildren) {
return false
}
for (let child of item.children) {
if (!this.isNextValueItemValueValid(child)) {
return false
}
}
return true
},
updateNextValueRecursiveIds(data) {
if (Array.isArray(data)) {
data.forEach((value, key) => {
data[key] = this.updateNextValueRecursiveIds(value)
})
} else {
data.id = this.makeId()
data.children = this.updateNextValueRecursiveIds(data.children)
}
return data
},
checkAndSaveNextValue() {
this.$refs.codeEditor.classList.toggle('is-invalid', false)
let hasError = false
try {
let data = JSON.parse(this.nextValue)
if (!Array.isArray(data)) {
hasError = true
} else {
for (let item of data) {
if (!this.isNextValueItemValueValid(item)) {
hasError = true
}
}
}
if (!hasError) {
this.value = this.updateNextValueRecursiveIds(data)
++this.blockKey
}
} catch (e) {
hasError = true
}
return this.$refs.codeEditor.classList.toggle('is-invalid', hasError)
codeUpdate(nextValue) {
this.value = nextValue
},
removeBlock(key) {
let newValue = []
@ -259,22 +161,6 @@ export default {
return data
},
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}`
},
},
components: {
BuilderBlockItem,
BuilderBlockCreate,
Draggable,
},
mounted() {
const that = this

View file

@ -0,0 +1,163 @@
<template>
<div>
<button
type="button"
class="btn btn-sm"
v-on:click="open"
>
<i class="fas fa-code"></i>
</button>
<dialog ref="dialog" class="modal-dialog modal-dialog-large builder-code-editor">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Code editor</h5>
<button
type="button"
class="close"
v-on:click="close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<textarea
class="form-control"
rows="10"
ref="codeEditor"
v-model="nextValue"
>
</textarea>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
v-on:click="close"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
v-on:click="checkAndSaveNextValue"
>
Save
</button>
</div>
</div>
</dialog>
</div>
</template>
<script>
export default {
name: 'BuilderBlockCodeEditor',
props: {
value: {
type: Array,
required: true,
},
widgets: {
type: Object,
required: true
},
},
data() {
return {
nextValue: null
}
},
methods: {
toJson(value) {
return JSON.stringify(value, null, 2)
},
open() {
this.nextValue = this.toJson(this.value)
this.$refs.dialog.showModal()
},
close() {
this.$refs.codeEditor.classList.toggle('is-invalid', false)
this.$refs.dialog.close()
},
isNextValueItemValueValid(item) {
const hasId = typeof item.id === 'string'
const hasWidget = typeof item.widget === 'string' && this.widgets[item.widget]
const hasSettings = typeof item.settings === 'object'
const hasChildren = Array.isArray(item.children)
if (!hasId || !hasWidget || !hasSettings || !hasChildren) {
return false
}
for (let child of item.children) {
if (!this.isNextValueItemValueValid(child)) {
return false
}
}
return true
},
updateNextValueRecursiveIds(data) {
if (Array.isArray(data)) {
data.forEach((value, key) => {
data[key] = this.updateNextValueRecursiveIds(value)
})
} else {
data.id = this.makeId()
data.children = this.updateNextValueRecursiveIds(data.children)
}
return data
},
checkAndSaveNextValue() {
this.$refs.codeEditor.classList.toggle('is-invalid', false)
let hasError = false
try {
let data = JSON.parse(this.nextValue)
if (!Array.isArray(data)) {
hasError = true
} else {
for (let item of data) {
if (!this.isNextValueItemValueValid(item)) {
hasError = true
}
}
}
if (!hasError) {
this.$emit('update', this.updateNextValueRecursiveIds(data))
this.close()
}
} catch (e) {
hasError = true
}
return this.$refs.codeEditor.classList.toggle('is-invalid', hasError)
},
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}`
},
},
components: {
},
mounted() {
},
created() {
},
updated() {
}
}
</script>

View file

@ -110,24 +110,39 @@
</Draggable>
<div v-if="widget.isContainer" class="container">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="widget.widgets"
position="bottom"
/>
<div class="d-flex justify-content-between">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="widget.widgets"
position="bottom"
/>
<BuilderBlockCodeEditor
ref="dialog"
:value="item.children"
:widgets="widgets"
@update="codeUpdate"
/>
</div>
</div>
</div>
</template>
<script>
import BuilderBlockCreate from './BuilderBlockCreate'
import BuilderBlockCodeEditor from './BuilderBlockCodeEditor'
import BuilderBlockSetting from './BuilderBlockSetting'
import Draggable from 'vuedraggable'
export default {
name: 'BuilderBlockItem',
components: {
BuilderBlockCreate,
BuilderBlockSetting,
BuilderBlockCodeEditor,
Draggable,
},
props: {
widgets: {
type: Object,
@ -164,6 +179,9 @@ export default {
removeMe() {
this.$emit('remove-item')
},
codeUpdate(nextValue) {
this.item.children = nextValue
},
removeBlock(key) {
let children = []
@ -184,11 +202,6 @@ export default {
++this.blockKey
},
},
components: {
BuilderBlockCreate,
BuilderBlockSetting,
Draggable,
},
mounted() {
this.widget = this.widgets[this.item.widget]
},