MoonShine 2.0. Что нового?

от автора

Последние полгода наше комьюнити CutCode работает над новой версией нашей open-source админ-панели MoonShine. И вот недавно состоялся релиз MoonShine 2. Давайте пройдемся по всем значимым изменениям! Конечно, в одной статье я не смогу осветить все нововведения, но попробую сделать это по-максимуму. Ну а также расскажу о ближайших планах на MoonShine 3.

Новые требования

Laravel >= 10.20

PHP >= 8.1

Новый подход

Если смотреть на MoonShine 2 чисто визуально, то отличий не так и много. Да, у нас немного изменились некоторые поля и компоненты, появилось верхнее меню, но все это мелочь по сравнению с изменениями под капотом — там мы переписали 90% кода. В MoonShine 2 полностью новое ядро, которое дает невероятное количество дополнительных возможностей для разработчиков.

Ресурсы

Ресурсы уже не будут прежними. Теперь базовый ресурс вообще ничего не знает о Eloquent моделях и может работать с любым другим хранилищем.

Мы реализовали ресурс для моделей и называется он ModelResource. Те, кто использовал MoonShine 1, даже не увидят разницы в подходе. Но здесь нужно обратить внимание на то, что ресурс теперь изолирован и вы можете написать реализацию своего хранилища: будь то данные из внешнего api или парсинг лог файлов. Тут уже вы ограничиваетесь только своей фантазией.

Страницы

Новый постоянный житель MoonShine. Теперь страницы это основа MoonShine. Да у нас уже были кастомные страницы (которых к слову теперь нет), но текущие страницы не имеют границ и могут работать даже без ресурса. Ответственность у страниц это отображение компонентов, которых может быть сколько угодно — они могут быть компонентами MoonShine, либо просто blade компонентами или даже livewire! В итоге можно сказать, что ресурс это бокс с общей логикой для набора страниц. Но и опять-таки страницы могут существовать и без ресурса. Профиль пользователя это просто MoonShine-страница, которую можно заменить на свою, тоже самое касается и дашборда (главной страницы)

public function components(): array {     return [         FormBuilder::make()->fields([             Block::make([                 Grid::make([                     Column::make([                         Heading::make('Text'),                          ID::make(),                         Hidden::make('Hidden'),                      ])->columnSpan(6),                     Column::make([                         Heading::make('Textarea'),                          Textarea::make('Textarea'),                         TinyMce::make('TinyMce'),                     ])->columnSpan(6),                 ]),                  LineBreak::make(),             ]),         ])->submit('Submit', ['class' => 'btn-lg btn-primary']),     ]; }

Слои

Как я уже говорил ранее, для вашего удобства мы уже написали ресурс для моделей — ведь все-таки мы используем Laravel, и я думаю в 99% случаев ресурсы будут на основе моделей. Также к ресурсу мы добавили готовые страницы:

  • для листинга записей (IndexPage),

  • добавления/редактирования(FormPage) ,

  • детальная(DetailPage).

Чтобы вам удобно было располагать и наполнять компоненты на этих страницах, мы добавили подход с использованием слоев (Layers). В итоге страницы выглядят следующим образом:

public function components(): array {     return array_merge(         $this->topLayer(),         $this->mainLayer(),         $this->bottomLayer(),     ); }

Теперь, для того чтобы добавить компоненты сверху, достаточно переопределить метод topLayer.

public function topLayer(): array {     return [         Flex::make([             Heading::make('Title')         ])             ->customAttributes(['class' => 'mb-4'])             ->justifyAlign('end')     ]; }

Поля

Всё что нужно знать сейчас о полях, это то, что они также изолированы от модели и могут работать и без нее. Более подробно мы обсудим поля чуть ниже, когда будем рассматривать новые фичи.

Компоненты

Компоненты теперь сердце MoonShine. Всё что вы видите в MoonShine сделано компонентами. Вы можете их добавлять, перемещать, добавлять свои. За счет компонентов мы получили полноценный конструктор, и теперь процесс работы с админ-панелью напоминает сборку кубиков конструктора LEGO — очень увлекательный процесс! Ну и само собой, появилось много новых компонентов и декораций из коробки, о которых вы узнаете в документации.

LayoutBuilder

Изменять структуру основного шаблона теперь проще некуда. Убирайте компоненты, добавляйте свои, используйте декорации — буквально рисуйте шаблон по-своему! Давайте рассмотрим пример, где мы убираем боковую панель и подключаем верхнее меню:

final class MoonShineLayout implements MoonShineLayoutContract {     public static function build(): LayoutBuilder     {         return LayoutBuilder::make([             Sidebar::make([                 Menu::make()->customAttributes(['class' => 'mt-2']),             ]),             LayoutBlock::make([                 Flash::make(),                 Header::make(),                 Content::make(),                 Footer::make()->copyright(fn (): string => <<<'HTML'                         © 2021-2023 Made with ❤️ by                         <a href="https://cutcode.dev"                             class="font-semibold text-primary hover:text-secondary"                             target="_blank"                         >                             CutCode                         a>                     HTML)->menu([                     'https://moonshine.cutcode.dev' => 'Documentation',                 ]),             ])->customAttributes(['class' => 'layout-page']),         ]);     } }

ActionButtons

Те, кто уже давно используют MoonShine, помнят, что у нас для кнопок в таблице были ItemAction, для массовых действий BulkAction, а в форме FormAction, а на детальной странице DetailAction, ах да еще общие Action для главной страницы сверху. Чуть сам не запутался пока писал) Но все это в прошлом! Встречайте — теперь кнопками правит ActionButton и эти красавцы умеют гораздо больше, чем толпа предыдущих сущностей вместе взятых.

ActionButton::make('Create', $resource->route('crud.create'));

Нужно вызвать модалку по клику, с любым содержимым или подтверждением действий? Не проблема, для этого есть метод — inModal или withConfirm.

ActionButton::make('Create', $resource->route('crud.create'))                 ->inModal(fn() => 'Create', FormBuilder::make()),

Хотите открыть offcanvas — пожалуйста, воспользуйтесь методом inOffCanvas.

ActionButton::make('Filtes', '#')             ->secondary()             ->icon('heroicons.outline.adjustments-horizontal')             ->inOffCanvas(                 fn (): array|string|null => __('moonshine::ui.filters'),                 fn (): FormBuilder => new FiltersForm()             )

Кнопка может переходить на любой урл, а также можно получить асинхронно контент или загрузить блейд фрагмент (об этом немного позже), а самое важное что рендерятся они где угодно в MoonShine и есть хелперы для вызова в блейд.

actionBtn('Create', route('example.url'))                 ->inModal(fn() => 'Create', async: true),

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

ActionButton::make('Create', $resource->route('crud.create'))                 ->onClick(fn() => 'alert()', 'prevent'),

FormBuilder

Прежде чем мы поговорим о новых возможностях полей, стоит заметить, что изменена их концепция. В MoonShine 1 поля были привязаны к ресурсу и их тяжело было применять за его пределами. Теперь поля можно рендерить отдельно где угодно, но есть и места где им самое место — и это конечно же форма! С приходом MoonShine 2 мы рады представить FormBuilder, c помощью которого вы можете легко наполнить форму полями и декорациями, указать какой тип данных будет у полей (TypeCasts, подробнее в документации), а также использовать Precognition или асинхронное сохранение.

TableBuilder

Еще одно важное место для хранения полей — TableBuilder. Теперь создать таблицу в MoonShine или за её пределами — не проблема. Наполняем её любыми данными и указываем тип данных с помощью TypeCasts. Как и с формами, поддерживается асинхронный режим. Появился инструмент для управления атрибутами ячеек и строк, теперь добавлять им классы, стили, да всё что угодно — можно через ComponentAttributeBag.

Fields

Как бы там ни было, но поля — это основа MoonShine, без них точно никуда. И они всюду. Давайте разберем, что появилось нового! Для начала еще раз повторюсь, что поля ничего не знают о моделях (за исключением полей отношений, тут уж никуда без моделей) и могут наполняться любыми данными, даже за пределами MoonShine. Также появились новые поля, о которых можно почитать подробнее в документации.

Помните фильтры? Ну так вот — их больше не существует и теперь поля могут быть фильтрами, а логику фильтрации вы сможете менять “на лету”.

Теперь можно получать доступ к вложенным элементам через точку (‘user.email’):

Preview::make('Email', 'user.email');

Также логику можно выносить в отдельные Apply-классы, и регистрировать в системе. О Apply классах и MoonShineRegister подробнее смотрите в документации.

Поле Enum прокачали и добавили возможность указывать заголовок и цвет бейджа прямо в Enum, достаточно просто использовать методы getColor/toString.

enum ColorEnum: string {     case Red = "R";      case Black = "B";          case White = "W";      public function getColor(): string     {         return match ($this->value) {             "R" => 'purple',             "B" => 'gray',             "W" => 'green'         };     }      public function toString(): string     {         return match ($this->value) {             "R" => 'Purple',             "B" => 'Gray',             "W" => 'Green'         };     } }

BelongsToMany теперь работает со всеми типами полей для pivot значений.

BelongsToMany::make('Categories', 'categories', resource: new CategoryResource())     ->fields(function () {         return [             Date::make('Created at')->format('d.m.Y'),         ];     })

Async

Select можно указать url и получать опции селекта асинхронно, достаточно только соблюдать структуру ответа, которую мы укажем в документации.

Select::make('Select')    ->async(route('async-search'))    ->searchable(),

UpdateOnPreview

Отобразить поля теперь можно в таблице в виде элемента формы и асинхронно сохранять при изменении.

Text::make('Title')     ->updateOnPreview(fn ($item) => $this->route('resource.update-column', $item->getKey()))

beforeRender/afterRender/changePreview/addAssets/onApply/onBeforeApple/onAfterApple/onAfterDelete

Мы улучшили интерфейс полей и добавили больше возможностей кастомизации без создания отдельного класса. Вот как это работает:

Text::make('Thumbnail')    // Добавили дополнительные скрипты и стили для поля     ->addAssets(['custom.js', 'custom.css'])    // В форме до элемента отобразили превью с картинкой     ->beforeRender(function (Text $field) {         return $field->preview();     })     // Изменили превью, вместо текста отображаем изображение     ->changePreview(function ($value) {         return view('moonshine::ui.image', [             'value' => $value ? Storage::url($value) : null,         ]);     })    // Изменили логику сохранения, где мы не просто сохраняем текст а загружаем изображение по указанному урл     ->onApply(function (Article $item, string $value) {         $path = 'thumbnail.jpg';          if ($value && $value !== $path) {             Storage::put($path, file_get_contents($value));             $item->thumbnail = $path;         }          return $item;     }),

Badge/link

Добавить ссылку к полю или обернуть его в badge теперь можно для всех текстовых полей

Email::make('Title')     ->badge('purple')     ->link(fn($value) => "mailto:$value", 'Go to'),

Json

Поле прокачали по-полной, принимает любые типы полей и даже может работать как отношение, отображая форму или таблицу прямо в основной форме

Json::make('Comments')     ->asRelation(new CommentResource())     ->fields([         ID::make(),         BelongsTo::make('Article')             ->setColumn('article_id')             ->searchable(),         BelongsTo::make('User')             ->setColumn('user_id'),         Text::make('Text')->required(),         Image::make('Files')             ->multiple()             ->removable()             ->disk('public')             ->dir('comments'),     ])     ->creatable()     ->removable()

Resource

Еще раз напомню, что в MoonShine 2 ресурс теперь может работать и не на основе моделей. Но все-таки ресурс с моделями у нас прямо в коробке. И он очень похож на тот, который мы использовали в первой версии. Но также появились и новые подходы, а именно:

  1. Раздельный подход, где у нас под каждый тип полей (для главной, формы, детальной страницы) свои отдельные методы.

  2. С публикацией всех страниц ресурса, которые вы сможете кастомизировать по-своему.

  3. Полностью пустой ресурс для нетривиальных задач.

Иконку можно менять прямо в ресурсе через атрибут

#[Icon('heroicons.users')] class ArticleResource extends ModelResource

Был прокачан поиск по ресурсу и по json, доступ к отношениям и многое другое (смотрим документацию).

Basic

Fragments

Появилась новая декорация Fragment, которая дает возможность использовать blade fragments, отмечать блоки на странице и подгружать их асинхронно.

Fragment::make([     TableBuilder::make(items: $this->getResource()->paginate())         ->fields($this->getResource()->getIndexFields())         ->buttons([             ...$this->getResource()->getIndexButtons(),         ]), ])->withName('crud-table'),

Generics

Мы стараемся улучшать интерфейс и внедрили аннотации с дженериками для нашего с вами удобства.

Произвольные правила авторизации

Будет полезно для тех, кто пишет пакеты для MoonShine. Теперь можно регистрировать собственные правила валидации и все они будут применяться в ресурсе.

public function boot(): void {     MoonShine::defineAuthorization(         static function (ResourceContract $resource, Model $user, string $ability, Model $item): bool {             $hasUserPermissions = in_array(                 HasMoonShinePermissions::class,                 class_uses_recursive($user),                 true             );              if (! $hasUserPermissions) {                 return true;             }              if (! $user->moonshineUserPermission) {                 return true;             }              return isset($user->moonshineUserPermission->permissions[$resource::class][$ability]);         }     ); }

Handlers

Удобные классы для реализации логики в MoonShine.

class ExportHandler extends Handler {     public function handle(): Response     {         // Logic     } }

Красота и сахар

Теперь прямо из коробки доступен компонент с верхним меню вместо левого сайдбара.

return LayoutBuilder::make([     TopBar::make([         Menu::make()->top(),     ]),     // ... ]);

Кастомизация темы и цветовой схемы. Фон, кнопки и другие элементы запросто можно перекрасить.

protected function theme(): array {     return [         'colors' => [             'body' => '#fff'         ]     ]; }

Директива с ассетами MoonShine, чтобы можно было использовать возможности MoonShine за её пределами.

<head>     @moonShineAssets head>

Sortable прямо в коробке и уже интегрированы в поля Json/Image/File

x-data="sortable"

Helpers для тех кому нравятся хелперы

moonshine() moonshineRegister() to_page() moonshineRequest() moonshineAssets() moonshineMenu() moonshineLayout() form() table() actionBtn() // ...

Toasts improvements

Toasts можно вызвать в js.

Stubs для всего

Да кстати, мы интегрировали Laravel Prompts во все консольные команды. Понять структуру кастомных полей или компонентов для разработчиков теперь будет куда проще. Да и установка MoonShine теперь сводится к одной команде.

php artisan moonshine:field php artisan moonshine:component php artisan moonshine:apply php artisan moonshine:controller php artisan moonshine:layout php artisan moonshine:page

Новый домен

Проект разрастается и переехал на новый домен — https://moonshine-laravel.com

План на 3.0

  • Интеграция Laravel Echo, наконец-таки вебсокеты

  • Multi tenancy

  • Бесконечная вложенность в меню

  • Асинхронное удаление полей и записей

  • Новые шаблоны и темы

  • 2FA

  • Вложенные ресурсы

Заключение

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

Работая по MoonShine v.2 мы реализовали много пожеланий участников нашего комьюнити, а некоторые разработчики втягиваются в разработку, реализовывая свои же предложения. Всех Laravel-разработчиков приглашаю присоединиться — использовать MoonShine2 в своих проектах и участвовать в open source разработке.

Данил Щуцкий, автор проекта CutCode.


ссылка на оригинал статьи https://habr.com/ru/articles/774832/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *