Compare commits

..

No commits in common. "master" and "v1.26.0" have entirely different histories.

30 changed files with 236 additions and 576 deletions

View file

@ -1,27 +1,5 @@
## [Unreleased] ## [Unreleased]
### Added
* feat(repository): add RepositoryQuery::addCaseInsensitiveFilters()
* feat(repository): add RepositoryQuery::addForcedFilterHandler()
### Fixed
* fix(crud/index): fix row attribute value render
* fix(crud/navigation_setting): fix form action
* fix(crud/template): use default route params
## [v1.27.0] - 2025-12-22
### Fixed
* fix(crud): use route params to redirect after a delation
### Added
* add option `removeItemButton: true` when applying choices.js
* feat(builder): allow to define `allowed_widgets` in form options
* feat(collection): add delete_attr, add_attr options
* feat(builder): allow to add block between children
* feat(builder): improve UI to add new block
* feat(settings): allow to edit a setting in plain page
* feat(crud/index): allow to define row attributes
## [v1.26.0] - 2025-03-17 ## [v1.26.0] - 2025-03-17
### Added ### Added
* FileUploadHandler: allow to upload multiple files * FileUploadHandler: allow to upload multiple files

View file

@ -5,7 +5,7 @@ namespace App\Core\BuilderBlock\Block\Bootstrap;
use App\Core\BuilderBlock\BuilderBlock; use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
abstract class BootstrapBlock extends BuilderBlock class BootstrapBlock extends BuilderBlock
{ {
public function configure() public function configure()
{ {

View file

@ -5,7 +5,7 @@ namespace App\Core\BuilderBlock\Block\Editor;
use App\Core\BuilderBlock\BuilderBlock; use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
abstract class EditorBlock extends BuilderBlock class EditorBlock extends BuilderBlock
{ {
public function configure() public function configure()
{ {

View file

@ -406,7 +406,7 @@ abstract class CrudController extends AdminController
$this->addFlash('success', 'The data has been removed.'); $this->addFlash('success', 'The data has been removed.');
} }
return $this->redirectToRoute($configuration->getPageRoute($route), $configuration->getPageRouteParams($route)); return $this->redirectToRoute($configuration->getPageRoute($route));
} }
protected function doFilter(Session $session, string $context = 'filter'): Response protected function doFilter(Session $session, string $context = 'filter'): Response

View file

@ -35,8 +35,6 @@ class NavigationSettingAdminController extends AdminController
$session = $request->getSession(); $session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId()); $lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId); $lastRequest = $session->get($lastRequestId);
$options = $entity->getOptions();
$optionView = $options['view'] ?? 'modal';
if (null !== $lastRequest && !$request->isMethod('POST')) { if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create( $fakeRequest = Request::create(
@ -66,19 +64,17 @@ class NavigationSettingAdminController extends AdminController
$session->set($lastRequestId, $request->request->get('form')); $session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.'); $this->addFlash('warning', 'The form is not valid.');
if ($optionView === 'modal') { return $this->redirect(sprintf(
return $this->redirect(sprintf( '%s?data-modal=%s',
'%s?data-modal=%s', $redirectTo,
$redirectTo, urlencode($request->getUri())
urlencode($request->getUri()) ));
));
}
} }
return $this->render('@Core/setting/navigation_setting_admin/edit.html.twig', [ return $this->render('@Core/setting/navigation_setting_admin/edit.html.twig', [
'form' => $form->createView(), 'form' => $form->createView(),
'entity' => $entity, 'entity' => $entity,
'options' => $options, 'options' => $event->getData()['options'],
'redirectTo' => $redirectTo, 'redirectTo' => $redirectTo,
]); ]);
} }

View file

@ -55,8 +55,6 @@ class SettingAdminController extends AdminController
$session = $request->getSession(); $session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId()); $lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId); $lastRequest = $session->get($lastRequestId);
$options = $entity->getOptions();
$optionView = $options['view'] ?? 'modal';
if (null !== $lastRequest && !$request->isMethod('POST')) { if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create( $fakeRequest = Request::create(
@ -84,13 +82,11 @@ class SettingAdminController extends AdminController
$session->set($lastRequestId, $request->request->get('form')); $session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.'); $this->addFlash('warning', 'The form is not valid.');
if ($optionView === 'modal') { return $this->redirect(sprintf(
return $this->redirect(sprintf( '%s?data-modal=%s',
'%s?data-modal=%s', $redirectTo,
$redirectTo, urlencode($request->getUri())
urlencode($request->getUri()) ));
));
}
} }
return $this->render('@Core/setting/setting_admin/edit.html.twig', [ return $this->render('@Core/setting/setting_admin/edit.html.twig', [

View file

@ -2,8 +2,6 @@
namespace App\Core\Crud; namespace App\Core\Crud;
use App\Core\Entity\EntityInterface;
/** /**
* class CrudConfiguration. * class CrudConfiguration.
* *
@ -30,7 +28,6 @@ class CrudConfiguration
protected string $sortableCollectionProperty = 'sortOrder'; protected string $sortableCollectionProperty = 'sortOrder';
protected ?string $defaultLocale = null; protected ?string $defaultLocale = null;
protected array $showActions = []; protected array $showActions = [];
protected array $listRowAttributes = [];
protected static $self; protected static $self;
@ -250,26 +247,6 @@ class CrudConfiguration
return $this->viewDatas[$context][$name] ?? $defaultValue; return $this->viewDatas[$context][$name] ?? $defaultValue;
} }
public function setListRowAttributes(string $context, array $attributes): self
{
$this->listRowAttributes[$context] = $attributes;
return $this;
}
public function getListRowAttributes(string $context, EntityInterface $entity): array
{
$attributes = $this->listRowAttributes[$context] ?? [];
foreach ($attributes as $key => $attribute) {
if (is_callable($attribute)) {
$attributes[$key] = $attribute($entity);
}
}
return $attributes;
}
// -- // --
public function setField(string $context, string $label, string $field, array $options): self public function setField(string $context, string $label, string $field, array $options): self

View file

@ -26,9 +26,6 @@ class NavigationSetting implements EntityInterface
#[ORM\Column(type: 'text', nullable: true)] #[ORM\Column(type: 'text', nullable: true)]
protected $value; protected $value;
#[ORM\Column(type: 'text', nullable: true)]
protected $options;
#[ORM\ManyToOne(targetEntity: Navigation::class, inversedBy: 'navigationSettings')] #[ORM\ManyToOne(targetEntity: Navigation::class, inversedBy: 'navigationSettings')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $navigation; protected $navigation;
@ -97,16 +94,4 @@ class NavigationSetting implements EntityInterface
return $this; return $this;
} }
public function getOptions()
{
return json_decode($this->options, true) ?? [];
}
public function setOptions(?array $options): self
{
$this->options = json_encode($options ?? []);
return $this;
}
} }

View file

@ -25,9 +25,6 @@ class Setting implements EntityInterface
#[ORM\Column(type: 'text', nullable: true)] #[ORM\Column(type: 'text', nullable: true)]
protected $value; protected $value;
#[ORM\Column(type: 'text', nullable: true)]
protected $options;
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@ -80,16 +77,4 @@ class Setting implements EntityInterface
return $this; return $this;
} }
public function getOptions()
{
return json_decode($this->options, true) ?? [];
}
public function setOptions(?array $options): self
{
$this->options = json_encode($options ?? []);
return $this;
}
} }

View file

@ -19,7 +19,6 @@ class BuilderType extends AbstractType
parent::buildView($view, $form, $options); parent::buildView($view, $form, $options);
$view->vars = array_replace($view->vars, [ $view->vars = array_replace($view->vars, [
'allowed_widgets' => $options['allowed_widgets'],
]); ]);
} }
@ -29,10 +28,7 @@ class BuilderType extends AbstractType
$resolver->setDefaults([ $resolver->setDefaults([
'compound' => false, 'compound' => false,
'allowed_widgets' => [],
]); ]);
$resolver->setAllowedTypes('allowed_widgets', 'array');
} }
public function getBlockPrefix() public function getBlockPrefix()

View file

@ -16,23 +16,12 @@ class CollectionType extends BaseCollectionType
{ {
parent::buildView($view, $form, $options); parent::buildView($view, $form, $options);
$classes = [
'add_attr' => 'collection-add',
'delete_attr' => 'text-right',
];
foreach ($classes as $key => $class) {
$options[$key]['class'] = $class.' '.($options[$key]['class'] ?? '');
}
$view->vars = array_replace($view->vars, [ $view->vars = array_replace($view->vars, [
'collection_name' => $options['collection_name'], 'collection_name' => $options['collection_name'],
'label_add' => $options['label_add'], 'label_add' => $options['label_add'],
'label_delete' => $options['label_delete'], 'label_delete' => $options['label_delete'],
'allow_add' => $options['allow_add'], 'allow_add' => $options['allow_add'],
'allow_delete' => $options['allow_delete'], 'allow_delete' => $options['allow_delete'],
'add_attr' => $options['add_attr'],
'delete_attr' => $options['delete_attr'],
'template_before_item' => $options['template_before_item'], 'template_before_item' => $options['template_before_item'],
'template_after_item' => $options['template_after_item'], 'template_after_item' => $options['template_after_item'],
]); ]);
@ -48,8 +37,6 @@ class CollectionType extends BaseCollectionType
'label_delete' => 'Delete', 'label_delete' => 'Delete',
'template_before_item' => null, 'template_before_item' => null,
'template_after_item' => null, 'template_after_item' => null,
'add_attr' => [],
'delete_attr' => [],
]); ]);
} }

View file

@ -3,7 +3,7 @@
namespace App\Core; namespace App\Core;
if (!defined('MURPH_VERSION')) { if (!defined('MURPH_VERSION')) {
define('MURPH_VERSION', 'v1.27.0'); define('MURPH_VERSION', 'v1.26.0');
} }
/** /**

View file

@ -18,7 +18,6 @@ abstract class RepositoryQuery
protected PaginatorInterface $paginator; protected PaginatorInterface $paginator;
protected string $id; protected string $id;
protected array $forcedFilterHandlers = []; protected array $forcedFilterHandlers = [];
protected array $caseInsensitiveFilters = [];
public function __construct(ServiceEntityRepository $repository, string $id, PaginatorInterface $paginator = null) public function __construct(ServiceEntityRepository $repository, string $id, PaginatorInterface $paginator = null)
{ {
@ -89,11 +88,7 @@ abstract class RepositoryQuery
$this->andWhere('.'.$name.' = :'.$name); $this->andWhere('.'.$name.' = :'.$name);
$this->setParameter(':'.$name, $value); $this->setParameter(':'.$name, $value);
} elseif (is_string($value)) { } elseif (is_string($value)) {
if (in_array($name, $this->caseInsensitiveFilters)) { $this->andWhere('.'.$name.' LIKE :'.$name);
$this->andWhere(sprintf('LOWER ( .%1$s) LIKE LOWER(:%1$s)', $name));
} else {
$this->andWhere('.'.$name.' LIKE :'.$name);
}
$this->setParameter(':'.$name, '%'.$value.'%'); $this->setParameter(':'.$name, '%'.$value.'%');
} else { } else {
$this->filterHandler($name, $value); $this->filterHandler($name, $value);
@ -147,18 +142,4 @@ abstract class RepositoryQuery
protected function filterHandler(string $name, $value) protected function filterHandler(string $name, $value)
{ {
} }
protected function addCaseInsensitiveFilters(string $name): self
{
$this->caseInsensitiveFilters[] = $name;
return $this;
}
protected function addForcedFilterHandlers(string $name): self
{
$this->forcedFilterHandlers[] = $name;
return $this;
}
} }

View file

@ -761,19 +761,16 @@ label.required::after {
} }
.builder-widget { .builder-widget {
.container {
max-width: 100%;
}
.block { .block {
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; border: 1px solid rgba(map-get($theme-colors, 'dark-blue'), 0.3);
padding: 15px; padding: 10px;
border-radius: 4px; border-radius: 4px;
margin-bottom: 10px;
background: rgba(map-get($theme-colors, 'dark-blue'), 0.02); background: rgba(map-get($theme-colors, 'dark-blue'), 0.02);
} }
> .block { > .block {
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px; border: 1px solid map-get($theme-colors, 'dark-blue');
} }
.block-header { .block-header {
@ -787,6 +784,9 @@ label.required::after {
} }
} }
$block-colors: #E183F5 #E3F7C6 #82DDF5 #F5BA82 #A088A6;
$block-colors-length: length($block-colors);
.block .block-icon { .block .block-icon {
> * { > * {
display: inline-block; display: inline-block;
@ -794,34 +794,36 @@ label.required::after {
} }
} }
.builder-add { @for $i from 1 through 100 {
&-top { $block-color-index: ($block-colors-length + $i) % $block-colors-length + 1;
margin-top: 7px;
}
&-button { .block-depth-#{$i} {
cursor: pointer; .block-label {
background: rgba(map-get($theme-colors, 'dark-blue'), 0.1); background: nth($block-colors, $block-color-index);
text-align: center; border: 1px solid darken(nth($block-colors, $block-color-index), 50%);
padding-bottom: 5px; color: darken(nth($block-colors, $block-color-index), 50%);
margin: 8px 0;
border-radius: 4px;
&:hover {
background: rgba(map-get($theme-colors, 'dark-blue'), 0.2);
} }
.btn { .builder-add .btn {
font-size: 12px; background: nth($block-colors, $block-color-index);
line-height: 14px; border: 1px solid darken(nth($block-colors, $block-color-index), 50%);
padding: 3px 5px; color: darken(nth($block-colors, $block-color-index), 50%);
} }
} }
} }
.block-root { .builder-add {
border: 1px solid map-get($theme-colors, 'dark-blue'); width: 100%;
box-shadow: none;
&-top {
margin-top: 7px;
}
.btn {
font-size: 12px;
line-height: 14px;
padding: 3px 5px;
}
} }
.block-root > .container .builder-add { .block-root > .container .builder-add {
@ -838,6 +840,10 @@ label.required::after {
} }
} }
.block .block {
margin-top: 10px;
}
.block-id { .block-id {
font-size: 12px; font-size: 12px;
margin-right: 5px; margin-right: 5px;
@ -868,32 +874,4 @@ label.required::after {
min-height: 50vh; min-height: 50vh;
} }
} }
.dragger {
cursor: pointer;
color: #6c757d;
border-color: #6c757d;
text-align: center;
vertical-align: middle;
}
$block-colors: #E183F5 #E3F7C6 #82DDF5 #F5BA82 #A088A6;
$block-colors-length: length($block-colors);
@for $i from 1 through 100 {
$block-color-index: ($block-colors-length + $i) % $block-colors-length + 1;
.block-depth-#{$i} {
.block-label {
background: nth($block-colors, $block-color-index);
border: 1px solid darken(nth($block-colors, $block-color-index), 50%);
color: darken(nth($block-colors, $block-color-index), 50%);
}
.builder-add-button:hover {
background: nth($block-colors, $block-color-index);
color: darken(nth($block-colors, $block-color-index), 50%);
}
}
}
} }

View file

@ -8,7 +8,7 @@
:container="value" :container="value"
:widgets="widgets" :widgets="widgets"
:openedBlocks="openedBlocks" :openedBlocks="openedBlocks"
:allowedWidgets="allowedWidgets" :allowedWidgets="[]"
v-if="value.length > 0" v-if="value.length > 0"
position="top" position="top"
/> />
@ -34,26 +34,16 @@
@remove-item="removeBlock(key)" @remove-item="removeBlock(key)"
@drag-start="dragStart" @drag-start="dragStart"
@drag-end="dragEnd" @drag-end="dragEnd"
> />
<template #action v-if="(key+1) !== value.length"> <div class="container">
<div class="d-flex justify-content-between">
<BuilderBlockCreate <BuilderBlockCreate
:container="value" :container="value"
:widgets="widgets" :widgets="widgets"
:openedBlocks="openedBlocks" :openedBlocks="openedBlocks"
:allowedWidgets="allowedWidgets" :allowedWidgets="[]"
:position="key" position="bottom"
/> />
</template>
</BuilderBlockItem>
<div class="container">
<BuilderBlockCreate
:container="value"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="allowedWidgets"
position="bottom"
/>
<div class="text-right">
<div> <div>
<BuilderBlockCodeEditor <BuilderBlockCodeEditor
ref="dialog" ref="dialog"
@ -100,11 +90,6 @@ export default {
initialValue: { initialValue: {
type: Array, type: Array,
required: false, required: false,
},
allowedWidgets: {
type: Array,
required: false,
default: [],
} }
}, },
data() { data() {

View file

@ -47,9 +47,9 @@
<template> <template>
<div class="builder-add" :class="{'builder-add-top': position === 'top'}"> <div class="builder-add" :class="{'builder-add-top': position === 'top'}">
<div class="builder-add-button" v-on:click="togglePicker"> <button type="button" class="btn btn-secondary" v-on:click="togglePicker">
+ <span class="fa fa-plus"></span>
</div> </button>
<div class="builder-block-picker mt-2 row" :class="{'d-none': !showPicker}"> <div class="builder-block-picker mt-2 row" :class="{'d-none': !showPicker}">
<div class="col-auto builder-block-picker-menu"> <div class="col-auto builder-block-picker-menu">
@ -116,7 +116,7 @@ export default {
required: true required: true
}, },
position: { position: {
type: [String, Number], type: String,
required: true required: true
}, },
}, },
@ -142,14 +142,10 @@ export default {
children: [], children: [],
} }
if (this.position === 'bottom') { if (this.position === 'bottom') {
this.container.push(block) this.container.push(block)
this.$emit('updateContainer', this.container)
} else if (this.position === 'top') {
this.container.unshift(block)
} else { } else {
this.container.splice(this.position+1, 0, block) this.container.unshift(block)
} }
this.$emit('updateContainer', this.container) this.$emit('updateContainer', this.container)

View file

@ -1,122 +1,116 @@
<template> <template>
<div> <div
<div class="block"
class="block" :class="'block-depth-' + depth"
:class="'block-depth-' + depth" v-if="widget"
v-if="widget" :key="blockKey"
:key="blockKey" >
> <div class="block-header d-flex justify-content-between">
<div class="block-header d-flex justify-content-between"> <div>
<div>
<div
class="block-header-item block-label"
:title="item.widget"
>
<span
class="block-icon"
v-if="widget.icon"
v-html="widget.icon"
>
</span>
{{ widget.label }}
</div>
<button
type="button"
class="block-header-item btn btn-sm btn-outline-secondary"
v-on:click="toggleSettings"
v-if="Object.keys(widget.settings).length"
>
<span class="fa fa-cog"></span>
</button>
<span class="fa fa-arrows-alt dragger"></span>
</div>
<div <div
v-if="widget.preview && typeof item.settings[widget.preview] == 'string'" class="block-header-item block-label"
class="block-preview" :title="item.widget"
> >
{{ truncate(item.settings[widget.preview]) }} <span
</div> class="block-icon"
v-if="widget.icon"
<div> v-html="widget.icon"
<span class="block-id">
{{ item.id }}
</span>
<button
type="button"
class="block-header-item btn btn-sm text-white bg-danger"
v-on:click="removeMe(item)"
> >
<span class="fa fa-trash"></span> </span>
</button>
{{ widget.label }}
</div> </div>
<button
type="button"
class="block-header-item btn btn-sm btn-outline-secondary"
v-on:click="toggleSettings"
v-if="Object.keys(widget.settings).length"
>
<span class="fa fa-cog"></span>
</button>
<button
type="button"
class="block-header-item btn btn-sm btn-outline-secondary dragger"
>
<span class="fa fa-arrows-alt dragger"></span>
</button>
</div> </div>
<div class="block-settings" v-if="Object.keys(widget.settings).length" :class="{'d-none': !showSettings}"> <div
<div class="row"> v-if="widget.preview && typeof item.settings[widget.preview] == 'string'"
<BuilderBlockSetting class="block-preview"
class="mb-0" >
v-for="(params, setting) in widget.settings" {{ truncate(item.settings[widget.preview]) }}
:key="item.id + '-' + setting"
:class="widget.class"
:item="item"
:params="params"
:setting="setting"
/>
</div>
</div> </div>
<div v-if="widget.isContainer" class="container"> <div>
<BuilderBlockCreate <span class="block-id">
:container="item.children" {{ item.id }}
:widgets="widgets" </span>
:openedBlocks="openedBlocks" <button
:allowedWidgets="widget.widgets" type="button"
v-if="item.children.length > 0" class="block-header-item btn btn-sm text-white bg-danger"
position="top" v-on:click="removeMe(item)"
>
<span class="fa fa-trash dragger"></span>
</button>
</div>
</div>
<div class="block-settings" v-if="Object.keys(widget.settings).length" :class="{'d-none': !showSettings}">
<div class="row">
<BuilderBlockSetting
class="mb-0"
v-for="(params, setting) in widget.settings"
:key="item.id + '-' + setting"
:class="widget.class"
:item="item"
:params="params"
:setting="setting"
/> />
</div> </div>
</div>
<Draggable <div v-if="widget.isContainer" class="container">
v-if="widget.isContainer" <BuilderBlockCreate
v-model="item.children" :container="item.children"
ghost-class="ghost" :widgets="widgets"
group="children" :openedBlocks="openedBlocks"
@start="dragStart" :allowedWidgets="widget.widgets"
@end="dragEnd" v-if="item.children.length > 0"
:animation="200" position="top"
handle=".dragger" />
class="block-dropzone" </div>
>
<template v-if="item.children !== null && item.children.length > 0">
<BuilderBlockItem
v-for="(child, key) in item.children"
:key="child.id"
:item="child"
:widgets="widgets"
:openedBlocks="openedBlocks"
:depth="depth + 1"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
>
<template #action v-if="(key+1) !== item.children.length">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:position="key"
:openedBlocks="openedBlocks"
:allowedWidgets="widget.widgets"
/>
</template>
</BuilderBlockItem>
</template>
</Draggable>
<div v-if="widget.isContainer" class="container"> <Draggable
v-if="widget.isContainer"
v-model="item.children"
ghost-class="ghost"
group="children"
@start="dragStart"
@end="dragEnd"
:animation="200"
handle=".dragger"
class="block-dropzone"
>
<BuilderBlockItem
v-if="item.children !== null && item.children.length > 0"
v-for="(child, key) in item.children"
:key="child.id"
:item="child"
:widgets="widgets"
:openedBlocks="openedBlocks"
:depth="depth + 1"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
/>
</Draggable>
<div v-if="widget.isContainer" class="container">
<div class="d-flex justify-content-between">
<BuilderBlockCreate <BuilderBlockCreate
:container="item.children" :container="item.children"
:widgets="widgets" :widgets="widgets"
@ -124,17 +118,14 @@
:allowedWidgets="widget.widgets" :allowedWidgets="widget.widgets"
position="bottom" position="bottom"
/> />
<div class="text-right"> <BuilderBlockCodeEditor
<BuilderBlockCodeEditor ref="dialog"
ref="dialog" :value="item.children"
:value="item.children" :widgets="widgets"
:widgets="widgets" @update="codeUpdate"
@update="codeUpdate" />
/>
</div>
</div> </div>
</div> </div>
<slot name="action"></slot>
</div> </div>
</template> </template>
@ -168,7 +159,7 @@ export default {
depth: { depth: {
type: Number, type: Number,
required: true required: true
}, }
}, },
data() { data() {
return { return {

View file

@ -3,7 +3,7 @@
<span v-if="params.label && params.type !== 'checkbox'" v-text="params.label"></span> <span v-if="params.label && params.type !== 'checkbox'" v-text="params.label"></span>
<input <input
v-if="['number', 'checkbox', 'text', 'color', 'range'].includes(params.type)" v-if="['number', 'checkbox', 'text'].includes(params.type)"
v-model="item.settings[setting]" v-model="item.settings[setting]"
v-bind="params.attr" v-bind="params.attr"
:type="params.type" :type="params.type"

View file

@ -12,14 +12,12 @@ module.exports = () => {
el: component, el: component,
template: `<BuilderBlock template: `<BuilderBlock
:initialValue="value" :initialValue="value"
:allowedWidgets="allowedWidgets"
name="${component.getAttribute('data-name')}" name="${component.getAttribute('data-name')}"
id="${component.getAttribute('data-id')}" id="${component.getAttribute('data-id')}"
/>`, />`,
data() { data() {
return { return {
value: JSON.parse(component.getAttribute('data-value')), value: JSON.parse(component.getAttribute('data-value'))
allowedWidgets: JSON.parse(component.getAttribute('data-allowedwidgets')),
} }
}, },
components: { components: {

View file

@ -4,7 +4,6 @@ module.exports = () => {
document.querySelectorAll('*[data-jschoice]').forEach((item) => { document.querySelectorAll('*[data-jschoice]').forEach((item) => {
return new Choices(item, { return new Choices(item, {
searchFields: ['label'], searchFields: ['label'],
removeItemButton: true,
}) })
}) })
} }

View file

@ -183,20 +183,17 @@
{% endif %} {% endif %}
{% block list_item %} {% block list_item %}
{%- set dbClick -%} {%- set dbClick %}
{%- if configuration.doubleClick(context) -%} {% if configuration.doubleClick(context) %}
{%- if configuration.action(context, 'show', true, [item]) -%} {% if configuration.action(context, 'show', true, [item]) %}
{{- path(configuration.pageRoute('show'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) -}} {{ path(configuration.pageRoute('show'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) }}
{%- elseif configuration.action(context, 'edit', true, [item]) -%} {% elseif configuration.action(context, 'edit', true, [item]) %}
{{- path(configuration.pageRoute('edit'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) -}} {{ path(configuration.pageRoute('edit'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) }}
{%- endif -%} {% endif %}
{%- endif -%} {% endif %}
{%- endset -%} {% endset -%}
{% set rowAttributes = configuration.listRowAttributes(context, item) %} <tr {{ dataSortableItem|raw }} {% if configuration.doubleClick(context) %}data-dblclick="{{- dbClick -}}"{% endif %} class="{{ loop.index is odd ? 'is-odd' : 'is-even' }}">
{% set rowClasses = rowAttributes['class']|default('') %}
<tr {{ dataSortableItem|raw }} {% if configuration.doubleClick(context) %}data-dblclick="{{- dbClick -}}"{% endif %} class="{{ loop.index is odd ? 'is-odd' : 'is-even' }} {{ rowClasses }}" {% for attr, attrValue in rowAttributes %}{% if attr != 'class' %}{{ attr }}="{{ attrValue }}" {% endif %}{% endfor %}>
{% if configuration.hasBatchAction(context) %} {% if configuration.hasBatchAction(context) %}
<td class="crud-batch-column"> <td class="crud-batch-column">
<input type="checkbox" class="batch_form" name="batch[items][{{ loop.index }}]" form="form-batch" value="{{ loop.index }}"> <input type="checkbox" class="batch_form" name="batch[items][{{ loop.index }}]" form="form-batch" value="{{ loop.index }}">

View file

@ -18,7 +18,7 @@
{% block header_actions_before %}{% endblock %} {% block header_actions_before %}{% endblock %}
{% if configuration.action(context, 'back', true) %} {% if configuration.action(context, 'back', true) %}
<a href="{{ path(configuration.pageRoute('index'), configuration.pageRouteParams('index')) }}" class="btn btn-light"> <a href="{{ path(configuration.pageRoute('index')) }}" class="btn btn-light">
<span class="fa fa-list pr-1"></span> <span class="fa fa-list pr-1"></span>
<span class="d-none d-md-inline"> <span class="d-none d-md-inline">
{{ configuration.actionTitle(context, 'back', 'Back to the list')|trans }} {{ configuration.actionTitle(context, 'back', 'Back to the list')|trans }}
@ -27,7 +27,7 @@
{% endif %} {% endif %}
{% if configuration.action(context, 'edit', true, [entity]) %} {% if configuration.action(context, 'edit', true, [entity]) %}
<a href="{{ path(configuration.pageRoute('edit'), {entity: entity.id}|merge(configuration.pageRouteParams('edit'))) }}" class="btn btn-primary"> <a href="{{ path(configuration.pageRoute('edit'), {entity: entity.id}) }}" class="btn btn-primary">
<span class="fa fa-edit pr-1"></span> <span class="fa fa-edit pr-1"></span>
<span class="d-none d-md-inline"> <span class="d-none d-md-inline">

View file

@ -1,5 +1,4 @@
{% apply spaceless %} {% apply spaceless %}
{% block html %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -63,5 +62,4 @@
{% endblock %} {% endblock %}
</body> </body>
</html> </html>
{% endblock %}
{% endapply %} {% endapply %}

View file

@ -3,14 +3,13 @@
{% block builder_widget %} {% block builder_widget %}
{% set row_attr = row_attr|merge({class: 'builder-widget ' ~ (row_attr.class ?? '')}) %} {% set row_attr = row_attr|merge({class: 'builder-widget ' ~ (row_attr.class ?? '')}) %}
{% set value = value is iterable ? value|json_encode : value %} {% set value = value is iterable ? value|json_encode : value %}
{% set allowed_widgets = allowed_widgets is iterable ? allowed_widgets|json_encode : allowed_widgets %}
{% if value == '' %} {% if value == '' %}
{% set value = '[]' %} {% set value = '[]' %}
{% endif %} {% endif %}
<div {% for attr, value in row_attr %}{{ attr }}="{{ value }}" {% endfor %}> <div {% for attr, value in row_attr %}{{ attr }}="{{ value }}" {% endfor %}>
<div class="builder-widget-component" data-value="{{ value }}" data-name="{{ full_name }}" data-id="{{ id }}" data-allowedwidgets="{{ allowed_widgets }}"> <div class="builder-widget-component" data-value="{{ value }}" data-name="{{ full_name }}" data-id="{{ id }}">
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@ -79,8 +78,6 @@
{% block collection_block_widget %} {% block collection_block_widget %}
{% set allow_delete = allow_delete|default(false) %} {% set allow_delete = allow_delete|default(false) %}
{% set allow_add = allow_add|default(false) %} {% set allow_add = allow_add|default(false) %}
{% set add_attr = add_attr|default({}) %}
{% set delete_attr = delete_attr|default({}) %}
<div data-collection="collection-{{ collection_name }}"> <div data-collection="collection-{{ collection_name }}">
{% for item in form.value %} {% for item in form.value %}
@ -90,7 +87,7 @@
{% endfor %} {% endfor %}
{% if allow_delete %} {% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}> <div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger"> <span data-collection-delete-container class="btn btn-sm btn-danger">
<span data-collection-delete="{{ loop.index }}"> <span data-collection-delete="{{ loop.index }}">
<span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span> <span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span>
@ -104,7 +101,7 @@
</div> </div>
{% if allow_add %} {% if allow_add %}
<div data-collection-add="collection-{{ collection_name }}" {% for k, v in add_attr %}{{ k }}="{{ v }}"{% endfor %}> <div data-collection-add="collection-{{ collection_name }}" class="collection-add">
<span class="btn btn-sm btn-primary" data-collection-add="collection-{{ collection_name }}"> <span class="btn btn-sm btn-primary" data-collection-add="collection-{{ collection_name }}">
<span data-collection-add="collection-{{ collection_name }}" class="fa fa-plus"></span> <span data-collection-add="collection-{{ collection_name }}" class="fa fa-plus"></span>
{{ label_add|trans }} {{ label_add|trans }}
@ -116,7 +113,7 @@
{{ form_rest(form.value.vars.prototype) }} {{ form_rest(form.value.vars.prototype) }}
{% if allow_delete %} {% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}> <div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger"> <span data-collection-delete-container class="btn btn-sm btn-danger">
{{ label_delete|trans }} {{ label_delete|trans }}
</span> </span>
@ -131,8 +128,6 @@
{% set attrs = attr|merge({class: 'mb-1 ' ~ (attr.class ?? '')}) %} {% set attrs = attr|merge({class: 'mb-1 ' ~ (attr.class ?? '')}) %}
{% set allow_delete = allow_delete|default(false) %} {% set allow_delete = allow_delete|default(false) %}
{% set allow_add = allow_add|default(false) %} {% set allow_add = allow_add|default(false) %}
{% set add_attr = add_attr|default({}) %}
{% set delete_attr = delete_attr|default({}) %}
<div data-collection="collection-{{ collection_name }}" {% for attr, value in row_attr %}{{ attr }}="{{ value }}"{% endfor %}> <div data-collection="collection-{{ collection_name }}" {% for attr, value in row_attr %}{{ attr }}="{{ value }}"{% endfor %}>
{% for item in form %} {% for item in form %}
@ -150,7 +145,7 @@
{% endif %} {% endif %}
{% if allow_delete %} {% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}> <div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger"> <span data-collection-delete-container class="btn btn-sm btn-danger">
<span data-collection-delete="{{ loop.index }}"> <span data-collection-delete="{{ loop.index }}">
<span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span> <span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span>
@ -164,7 +159,7 @@
</div> </div>
{% if allow_add %} {% if allow_add %}
<div data-collection-add="collection-{{ collection_name }}" {% for k, v in add_attr %}{{ k }}="{{ v }}"{% endfor %}> <div data-collection-add="collection-{{ collection_name }}" class="collection-add">
<span class="btn btn-sm btn-primary" data-collection-add="collection-{{ collection_name }}"> <span class="btn btn-sm btn-primary" data-collection-add="collection-{{ collection_name }}">
<span data-collection-add="collection-{{ collection_name }}" class="fa fa-plus"></span> <span data-collection-add="collection-{{ collection_name }}" class="fa fa-plus"></span>
{{ label_add|trans }} {{ label_add|trans }}
@ -176,7 +171,7 @@
{{ form_rest(form.vars.prototype) }} {{ form_rest(form.vars.prototype) }}
{% if allow_delete %} {% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}> <div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger"> <span data-collection-delete-container class="btn btn-sm btn-danger">
{{ label_delete|trans }} {{ label_delete|trans }}
</span> </span>

View file

@ -1,77 +1,20 @@
{% extends '@Core/admin/layout.html.twig' %} <div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}">
<div class="modal-content">
{% set view = entity.options['view']|default('modal') %} <div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
{% block html %} <button type="button" class="close" data-dismiss="modal" aria-label="Close">
{% if view == 'modal' %} <span aria-hidden="true">&times;</span>
<div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}"> </button>
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="{{ path('admin_navigation_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{{ include('@Core/setting/navigation_setting_admin/_form.html.twig') }}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
</div> </div>
{% else %} <div class="modal-body">
{{ parent() }} <form action="{{ path('admin_navigation_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{% endif %} {{ include('@Core/setting/navigation_setting_admin/_form.html.twig') }}
{% endblock %} </form>
{% block body_class %}has-form{% endblock %}
{% block title %}{{ entity.label }}{% endblock %}
{% block body %}
{% block header %}
<div class="bg-light">
<div class="crud-header">
{% block header_title %}
<h1 class="crud-header-title">{{ entity.label }}</h1>
{% endblock %}
{% block header_actions %}
<div class="crud-header-actions">
<div class="btn-group">
<a href="{{ path('admin_setting_index') }}" class="btn btn-light">
<span class="fa fa-list pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Back to the list'|trans }}
</span>
</a>
<button type="submit" form="form-main" class="btn btn-primary">
<span class="fa fa-save pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Save'|trans|build_string(entity) }}
</span>
</button>
</div>
</div>
{% endblock %}
</div>
</div> </div>
{% endblock %} <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
</div>
{% block form %}
<form action="{{ app.request.uri }}" method="post" id="form-main" enctype="multipart/form-data">
<div class="tab-content">
<div class="tab-pane active">
<div class="tab-form">
{{ include('@Core/setting/navigation_setting_admin/_form.html.twig') }}
</div>
</div>
</div>
</form>
{% endblock %}
{% endblock %}

View file

@ -1,77 +1,20 @@
{% extends '@Core/admin/layout.html.twig' %} <div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}">
<div class="modal-content">
{% set view = entity.options['view']|default('modal') %} <div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
{% block html %} <button type="button" class="close" data-dismiss="modal" aria-label="Close">
{% if view == 'modal' %} <span aria-hidden="true">&times;</span>
<div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}"> </button>
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="{{ path('admin_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{{ include('@Core/setting/setting_admin/_form.html.twig') }}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
</div> </div>
{% else %} <div class="modal-body">
{{ parent() }} <form action="{{ path('admin_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{% endif %} {{ include('@Core/setting/setting_admin/_form.html.twig') }}
{% endblock %} </form>
{% block body_class %}has-form{% endblock %}
{% block title %}{{ entity.label }}{% endblock %}
{% block body %}
{% block header %}
<div class="bg-light">
<div class="crud-header">
{% block header_title %}
<h1 class="crud-header-title">{{ entity.label }}</h1>
{% endblock %}
{% block header_actions %}
<div class="crud-header-actions">
<div class="btn-group">
<a href="{{ path('admin_setting_index') }}" class="btn btn-light">
<span class="fa fa-list pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Back to the list'|trans }}
</span>
</a>
<button type="submit" form="form-main" class="btn btn-primary">
<span class="fa fa-save pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Save'|trans|build_string(entity) }}
</span>
</button>
</div>
</div>
{% endblock %}
</div>
</div> </div>
{% endblock %} <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
</div>
{% block form %}
<form action="{{ app.request.uri }}" method="post" id="form-main" enctype="multipart/form-data">
<div class="tab-content">
<div class="tab-pane active">
<div class="tab-form">
{{ include('@Core/setting/setting_admin/_form.html.twig') }}
</div>
</div>
</div>
</form>
{% endblock %}
{% endblock %}

View file

@ -22,35 +22,21 @@
</thead> </thead>
<tbody> <tbody>
{% for item in pager %} {% for item in pager %}
{% set view = item.options['view']|default('modal') %}
{% set edit = path('admin_setting_edit', {entity: item.id, redirectTo: app.request.pathInfo}) %} {% set edit = path('admin_setting_edit', {entity: item.id, redirectTo: app.request.pathInfo}) %}
<tr> <tr>
<td class="col-5"> <td class="col-5">
{% if view == 'modal' %} <a href="#" data-modal="{{ edit }}" data-modal-create class="font-weight-bold text-body d-block">
<a href="#" data-modal="{{ edit }}" data-modal-create class="font-weight-bold text-body d-block"> {{ item.label|trans }}
{{ item.label }} </a>
</a>
{% else %}
<a href="{{ edit }}" class="font-weight-bold text-body d-block">
{{ item.label }}
</a>
{% endif %}
</td> </td>
<td class="col-5"> <td class="col-5">
<span class="btn btn-light">{{ item.section|trans }}</span> <span class="btn btn-light">{{ item.section|trans }}</span>
</td> </td>
<td class="col-2 miw-100 text-right"> <td class="col-2 miw-100 text-right">
{% if view == 'modal' %} <span data-modal="{{ edit }}" data-modal-create class="btn btn-sm btn-primary mr-1">
<span data-modal="{{ edit }}" data-modal-create class="btn btn-sm btn-primary mr-1"> <span data-modal="{{ edit }}" class="fa fa-edit"></span>
<span data-modal="{{ edit }}" class="fa fa-edit"></span> </span>
</span>
{% else %}
<a href="{{ edit }}" class="btn btn-sm btn-primary mr-1">
<span class="fa fa-edit"></span>
</a>
{% endif %}
<button type="submit" form="form-delete-{{ item.id }}" class="btn btn-sm btn-danger"> <button type="submit" form="form-delete-{{ item.id }}" class="btn btn-sm btn-danger">
<span class="fa fa-trash"></span> <span class="fa fa-trash"></span>
</button> </button>

View file

@ -54,36 +54,21 @@
</thead> </thead>
<tbody> <tbody>
{% for item in datas.settings %} {% for item in datas.settings %}
{% set view = item.options['view']|default('modal') %}
{% set edit = path('admin_navigation_setting_edit', {entity: item.id, redirectTo: app.request.pathInfo}) %} {% set edit = path('admin_navigation_setting_edit', {entity: item.id, redirectTo: app.request.pathInfo}) %}
<tr data-dblclick="{{ edit }}"> <tr data-dblclick="{{ edit }}">
<td class="col-5"> <td class="col-5">
{% if view == 'modal' %} <a href="#" data-modal="{{ edit }}" class="font-weight-bold text-body d-block">
<a href="#" data-modal="{{ edit }}" data-modal-create class="font-weight-bold text-body d-block"> {{ item.label|trans }}
{{ item.label }} </a>
</a>
{% else %}
<a href="{{ edit }}" class="font-weight-bold text-body d-block">
{{ item.label }}
</a>
{% endif %}
</td> </td>
<td class="col-5"> <td class="col-5">
<span class="btn btn-light">{{ item.section|trans }}</span> <span class="btn btn-light">{{ item.section|trans }}</span>
</td> </td>
<td class="col-2 miw-100 text-right"> <td class="col-2 miw-100 text-right">
{% if view == 'modal' %} <span data-modal="{{ edit }}" class="btn btn-sm btn-primary mr-1">
<span data-modal="{{ edit }}" data-modal-create class="btn btn-sm btn-primary mr-1"> <span data-modal="{{ edit }}" class="fa fa-edit"></span>
<span data-modal="{{ edit }}" class="fa fa-edit"></span> </span>
</span>
{% else %}
<a href="{{ edit }}" class="btn btn-sm btn-primary mr-1">
<span class="fa fa-edit"></span>
</a>
{% endif %}
<button type="submit" form="form-delete-{{ item.id }}" class="btn btn-sm btn-danger"> <button type="submit" form="form-delete-{{ item.id }}" class="btn btn-sm btn-danger">
<span class="fa fa-trash"></span> <span class="fa fa-trash"></span>
</button> </button>

View file

@ -24,14 +24,7 @@ class NavigationSettingManager
) { ) {
} }
public function init( public function init($navigation, string $code, string $section, string $label, $value = null)
$navigation,
string $code,
string $section,
string $label,
$value = null,
array $options = [],
)
{ {
$entity = $this->get($this->getNavigation($navigation), $code); $entity = $this->get($this->getNavigation($navigation), $code);
$isNew = null === $entity; $isNew = null === $entity;
@ -44,7 +37,6 @@ class NavigationSettingManager
$entity $entity
->setSection($section) ->setSection($section)
->setLabel($label) ->setLabel($label)
->setOptions($options)
; ;
if ($isNew) { if ($isNew) {

View file

@ -21,13 +21,7 @@ class SettingManager
) { ) {
} }
public function init( public function init(string $code, string $section, string $label, $value = null)
string $code,
string $section,
string $label,
$value = null,
array $options = [],
)
{ {
$entity = $this->get($code); $entity = $this->get($code);
$isNew = null === $entity; $isNew = null === $entity;
@ -40,7 +34,6 @@ class SettingManager
$entity $entity
->setSection($section) ->setSection($section)
->setLabel($label) ->setLabel($label)
->setOptions($options)
; ;
if ($isNew) { if ($isNew) {