MoonShine 2.5 "Mojito Supreme"

MoonShine 2.5 "Mojito Supreme"

Danil Shutsky
Danil Shutsky
21.12.2023 в 10:47

На этой неделе команда MoonShine выпустила релиз v.2.5.0 с кодовым именем "Mojito Supreme"! Давайте взглянем на самое интересное в этом обновлении!

AsyncMethod

Самая главная фича релиза - это возможность указывать в ActionButton и FormBuilder просто название метода в ресурсе или на странице и асинхронно его вызывать.

Основаная идея - сэкономить время для простой логики, где не хочется создавать отдельный контроллер.

Давайте взглянем на примеры:

ActionButton::make('Click me')->method('updateSomething')
FormBuilder::make()->asyncMethod('updateSomething')
// With toast
public function updateSomething(MoonShineRequest $request)
{
    // $request->getResource();
    // $request->getResource()->getItem();
    // $request->getPage();

    MoonShineUI::toast('MyMessage', 'success');

    return back();
}

// Exception
public function updateSomething(MoonShineRequest $request)
{
    throw new \Exception('My message');
}

// Custom json response
public function updateSomething(MoonShineRequest $request)
{
    return response()->json(['message' => 'MyMessage']);
}

Подробности в PR

Подробности в PR

Поле-портянка Template

Поле, которое по сути не имеет никакой начинки, и вы можете его конструировать налету, прямо в процессе объявления. В примере ниже мы реализовали HasOne отношение прямо в основной форме, при этом изменили и процесс сохранения и визуальное отображение. Полный простор для фантазии!

Template::make('Comment')
  ->changeFill(fn (Article $data) => $data->comment)
  ->changePreview(fn($data) => $data?->id ?? '-')
  ->fields((new CommentResource())->getFormFields())
  ->changeRender(function (?Comment $data, Template $field) {
      $fields = $field->preparedFields();
      $fields->fill($data?->toArray() ?? [], $data ?? new Comment());
  
      return Components::make($fields);
  })
  ->onAfterApply(function (Article $item, array $value) {
      $item->comment()->updateOrCreate([
          'id' => $value['id']
      ], $value);
  
      return $item;
  })

Подробности в PR

Подробности в PR

Кастомизация для ActionButtons где только можно

Теперь вы можете кастомизировать кнопки добавлени или удаления я в таблицах. Давайте рассмотрим несколько примеров.

Кнопка создания для TableBuilder:

TableBuilder::make()
  ->fields([
      ID::make(),
      Text::make('Title'),
  ])
  ->creatable(
      limit: 3,
      label: 'New',
      icon: 'heroicons.outline.pencil',
      attributes: ['class' => 'my-class']
  )

Изменили заголовок, иконку и добавили атрибуты. Также Вы можете заменить кнопку на свою:

TableBuilder::make()
  ->fields([
      ID::make(),
      Text::make('Title'),
  ])
  ->creatable(
      button: ActionButton::make('Foo', '#')
  )

Давайте также взглянем на поле Json, где мы можем кастомизировать кнопку добавить, также можем изменить кнопку удалить или добавить свой набор кнопок:

Json::make('Data', 'data.content')->fields([
    Text::make('Title'),
    Image::make('Image'),
    Text::make('Value'),
])->creatable(
    button: ActionButton::make(
        'New', '#',
    )->primary()
),
Json::make('Data', 'data.content')->fields([
    Text::make('Title'),
    Image::make('Image'),
    Text::make('Value'),
])->buttons([
    ActionButton::make('', '#')
        ->icon('heroicons.outline.trash')
        ->onClick(fn() => 'remove()', 'prevent')
        ->customAttributes(['class' => 'btn-secondary'])
        ->showInLine()
])

Давайте рассмотрим еще примеры по кнопкам удаления в других полях:

 Image::make('Thumbnail')
    ->removable(attributes: ['@click.prevent' => '$event.target.closest(`.x-removeable`).remove()'])
Json::make('Data', 'data.content')->fields([
    Text::make('Title'),
    Image::make('Image'),
    Text::make('Value'),
])
    ->removable(attributes: ['@click.prevent' => 'customAsyncRemove'])
    ->creatable(),

Подробности в PR

Подробности в PR

BelongsToMany возможность добавить свои кнопки и checkAll метод

Проще всего будет рассмотреть пример, где мы добавляем две дополнительные кнопки, которые активируют все элементы, либо наоборот:

BelongsToMany::make('Categories')->buttons([
    ActionButton::make('Check all', '')
        ->onClick(fn() => 'checkAll', 'prevent'),

    ActionButton::make('Uncheck all', '')
        ->onClick(fn() => 'uncheckAll', 'prevent')
])

А также сахар, где под капотом пример выше (так как думаю он будет иметь популярность):

BelongsToMany::make('Categories')->withCheckAll()

Ну и кнопку добавления также можно кастомизировать под себя:

BelongsToMany::make('Categories')
    ->creatable(
        button: ActionButton::make('Custom button', '')
    )

Подробности в PR

Хлебные крошки

Появилась возможность изменять хлебные крошки прямо из ресурса и для ViewPage:

protected function onBoot(): void
{
    $this->formPage()
        ->setBreadcrumbs([
            '#' => $this->getItem()?->user?->name
        ]);
}

Подробности в PR

Автозакрытие модалок

По умолчанию модальные окна при успешном сохранении формы закрывались, но теперь это поведение можно контролировать (autoClose)

Modal::make(
    'Demo modal',
    static fn() => FormBuilder::make(route('alert.post'))
        ->fields([
            Text::make('Text'),
        ])
        ->submit('Send', ['class' => 'btn-primary'])
        ->async(),
)
    ->name('demo-modal')
    ->autoClose(false), //Modal window don't close when form is submitted

ActionButton::make('Open modal', '#')
    ->onClick(fn() => "\$dispatch('modal-toggled-demo-modal')", 'prevent')
ActionButton::make('AsyncTest', '#')
    ->inModal(fn (): string => 'Go test',
        fn (): string => (string) FormBuilder::make(route('alert.post'), method: 'GET')
            ->fields([
                Text::make('Text'),
            ])
            ->submit('Отправить', ['class' => 'btn-primary'])
            ->async(),
            autoClose: false //Modal window don't close when form is submitted
    ),

Подробности в PR

Routes

Сахар для роутов

$resource->url(); // First page of the resource

$resource->route($name, $key, $params); // Advanced method for obtaining routes

$resource->indexPageUrl(); // index page
$resource->indexPageUrl(['query-tag' => $tag->uri()]); // query tag

$resource->formPageUrl(); // create page
$resource->formPageUrl(1); // edit page by int
$resource->formPageUrl($item); // edit page by Model

$resource->detailPageUrl(1); // detail page by int
$resource->detailPageUrl($item); // detail page by Model

$resource->asyncMethodUrl('updateSomething'); // ANY
$resource->fragmentLoadUrl('table-index', $resource->formPage());

// CRUD
$resource->route('crud.update', $data->getKey()); // PUT
$resource->route('crud.store')); // POST
$resource->route('crud.destroy', $data->getKey()); // DELETE
$resource->route('crud.massDelete'); // DELETE

// Handlers
$resource->route('handler', query: ['handlerUri' => $export->uriKey()]);
$page->url(); // page url
$page->route($params);  // Advanced method for obtaining routes


$page->asyncMethodUrl('updateSomething'); // ANY

$page->fragmentLoadUrl('table-index');

Подробности в PR

Генерация тестов

При генерации ресурса также появилась опция с генерацией тестового класса (Pest также поддерживаются)

php artisan make:moonshine NameOfResource --test
php artisan make:moonshine NameOfResource --pest

Подробности в PR

Визуальные правки и небольшие улучшения

  • Поля отношений в Block на детальной странице Pull request
  • Новые компоненты Components и FieldsGroupPull request
  • Возможность выровнять Tabs по левому и правому краю, либо по центру @k-rustemuly in Pull request
  • Обновились до Vite 5Pull request
  • Удалили последений border в таблицах Pull request

Исправлены баги 🐞

  • Breaking Change Очень важные изменения в классе MoonShine Pull request
  • Json и Template поля поддерживают asyncSearch в дочерних поляхPull request
  • Исправлена проблема полноэкранного режима TinyMce с TopBar Pull request
  • Проблема перезаписи при публикации ассетов Pull request
  • MongoDB Models fixPull request

https://github.com/moonshine-software/moonshine/compare/2.4.0...2.5.0