Advanced

Разработка пакетов

Основы

Основой MoonShine являются пакеты Laravel. Если вы новичок в разработке пакетов Laravel, вот несколько ресурсов, которые помогут вам понять основные концепции:

ServiceProvider

Через ServiceProvider вашего пакета вы можете автоматически добавлять ресурсы, страницы, создавать меню и правила авторизации, и многое другое.

 namespaces
namespace Author\MoonShineMyPackage;
 
use Illuminate\Support\ServiceProvider;
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Laravel\DependencyInjection\MoonShine;
 
class MyPackageServiceProvider extends ServiceProvider
{
/** @param MoonShine $core */
public function boot(CoreContract $core): void
{
$core
->resources([
MyPackageResource::class
])
->page([
MyPackagePage::class
]);
}
}

Также вы можете взаимодействовать с MenuManager.

 namespaces
namespace Author\MoonShineMyPackage;
 
use Illuminate\Support\ServiceProvider;
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Contracts\MenuManager\MenuManagerContract;
use MoonShine\Laravel\DependencyInjection\MoonShine;
 
class MyPackageServiceProvider extends ServiceProvider
{
/** @param MoonShine $core */
public function boot(
CoreContract $core,
MenuManagerContract $menu
): void
{
$menu->add([
MenuItem::make('MyPackagePage', MyPackagePage::class)
]);
}
}

Также вы можете взаимодействовать с AssetManager или ColorManager.

 namespaces
use MoonShine\Contracts\AssetManager\AssetManagerContract;
 
// ...
 
public function boot(
CoreContract $core,
AssetManagerContract $assets
): void
{
$assets->add([
InlineCss::make('body {background: red;}')
]);
}
 namespaces
use MoonShine\Contracts\ColorManager\ColorManagerContract;
 
// ...
 
public function boot(
CoreContract $core,
ColorManagerContract $colors
): void
{
$colors
->background('#A3C3D9')
->content('#A3C3D9')
->tableRow('#AE76A6')
->dividers('#AE76A6')
->borders('#AE76A6')
->buttons('#AE76A6')
->primary('#CCD6EB')
->secondary('#AE76A6');
}

Если вам нужно добавить дополнительную логику авторизации в приложение или во внешний пакет, используйте метод authorizationRules().

 namespaces
use MoonShine\Contracts\Core\DependencyInjection\ConfiguratorContract;
use MoonShine\Laravel\DependencyInjection\MoonShineConfigurator;
 
// ...
 
/**
* @param MoonShineConfigurator $configurator
*/
public function boot(ConfiguratorContract $configurator): void
{
$configurator->authorizationRules(
static function (ResourceContract $resource, Model $user, Ability $ability): bool {
return true;
}
);
}

Вы также можете прямо из ServiceProvider добавлять компоненты на страницы.

public function boot(): void
{
ProfilePage::pushComponent(
fn() => MyPackageComponent::make()
);
}

Не забудьте автоматически подключить ваш ServiceProvider в composer.json.

"extra": {
"laravel": {
"providers": [
"Author\\MoonShineMyPackage\\MyPackageServiceProvider"
]
}
}

Traits

Вы также можете включать в свой пакет трейты для ресурсов или страниц и изменять логику с помощью load{TraitName}()/boot{TraitName}() магических методов.

trait HasMyPackageTrait
{
public function loadHasMyPackageTrait(): void
{
$this->getFormPage()->addAssets([
Js::make('vendor/my-package/js/app.js'),
Css::make('vendor/my-package/css/app.css'),
]);
}
 
public function modifyFormComponent(ComponentContract $component): ComponentContract
{
return parent::modifyFormComponent($component)->fields([
Modal::make(
'This is my package modal.',
''
),
...$component->getFields()->toArray(),
]);
}
}

Пример пользовательского поля

Давайте быстро рассмотрим создание собственного поля! Это будет визуальный редактор на основе плагина Quill.js.

Создадим поле с помощью команды moonshine:field и выберем, что оно расширяет Textarea.

php artisan moonshine:field Quill

Удалим ненужные методы и добавим css/js.

 namespaces
namespace App\MoonShine\Fields;
 
use MoonShine\AssetManager\Css;
use MoonShine\AssetManager\Js;
use MoonShine\UI\Fields\Textarea;
 
final class Quill extends Textarea
{
protected string $view = 'moonshine-quill::fields.quill';
 
public function assets(): array
{
return [
Css::make('/css/moonshine/quill/quill.snow.css'),
Js::make('/js/moonshine/quill/quill.js'),
Js::make('/js/moonshine/quill/quill-init.js'),
];
}
}

Также изменим представление поля:

<div x-data="quill">
<div class="ql-editor" :id="$id('quill')" style="height: auto;">{!! $value ?? '' !!}</div>
 
<x-moonshine::form.textarea
:attributes="$attributes->merge([
'class' => 'ql-textarea',
'style' => 'display: none;'
])->except('x-bind:id')"
>{!! $value ?? '' !!}</x-moonshine::form.textarea>
</div>

Мы взяли quill.snow.css и quill.js из библиотеки, а инициализация JS с использованием Alpine.js представлена ниже.

document.addEventListener('alpine:init', () => {
Alpine.data('quill', () => ({
textarea: null,
editor: null,
 
init() {
this.textarea = this.$root.querySelector('.ql-textarea')
this.editor = this.$root.querySelector('.ql-editor')
 
const t = this
 
this.$nextTick(function() {
let quill = new Quill(`#${t.editor.id}`, {
theme: 'snow'
});
 
quill.on('text-change', () => {
t.textarea.value = t.editor.innerHTML || '';
t.textarea.dispatchEvent(new Event('change'));
});
})
},
}))
})

Пример кода этого поля можно найти в репозитории.