first upload all files

This commit is contained in:
NW
2023-06-11 13:14:03 +01:00
parent f14dbc52b5
commit c08b36d1b6
1705 changed files with 106852 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
<?php
namespace Modules\Menu\Admin;
use Modules\Admin\Ui\Tab;
use Modules\Admin\Ui\Tabs;
use Modules\Page\Entities\Page;
use Modules\Menu\Entities\MenuItem;
use Modules\Category\Entities\Category;
class MenuItemTabs extends Tabs
{
/**
* Make new tabs with groups.
*
* @return void
*/
public function make()
{
$this->group('menu_item_information', trans('menu::menu_items.tabs.group.menu_item_information'))
->active()
->add($this->general())
->add($this->image());
}
private function general()
{
return tap(new Tab('general', trans('menu::menu_items.tabs.general')), function (Tab $tab) {
$tab->active();
$tab->weight(5);
$tab->view('menu::admin.menu_items.tabs.general', [
'categories' => $this->categories(),
'pages' => $this->pages(),
'parentMenuItems' => $this->parentMenuItems(),
]);
});
}
private function categories()
{
return Category::where('parent_id', null)->get()->sortBy('name')->pluck('name', 'id')
->prepend(trans('menu::menu_items.form.select_category'), '');
}
private function pages()
{
return Page::all()->sortBy('name')->pluck('name', 'id')
->prepend(trans('menu::menu_items.form.select_page'), '');
}
private function parentMenuItems()
{
$parentMenuItems = ['' => trans('menu::menu_items.form.select_parent')];
return $parentMenuItems += MenuItem::parents(request('menuId'), request('id'));
}
private function image()
{
return tap(new Tab('image', trans('menu::menu_items.tabs.image')), function (Tab $tab) {
if (! auth()->user()->hasAccess('admin.media.index')) {
return;
}
$tab->weight(10);
$tab->view('menu::admin.menu_items.tabs.image');
});
}
}

View File

@@ -0,0 +1,22 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Define which assets will be available through the asset manager
|--------------------------------------------------------------------------
| These assets are registered on the asset manager
*/
'all_assets' => [
'admin.menu.css' => ['module' => 'menu:admin/css/menu.css'],
'admin.menu.js' => ['module' => 'menu:admin/js/menu.js'],
],
/*
|--------------------------------------------------------------------------
| Define which default assets will always be included in your pages
| through the asset pipeline
|--------------------------------------------------------------------------
*/
'required_assets' => [],
];

View File

@@ -0,0 +1,16 @@
<?php
return [
'admin.menus' => [
'index' => 'menu::permissions.menus.index',
'create' => 'menu::permissions.menus.create',
'edit' => 'menu::permissions.menus.edit',
'destroy' => 'menu::permissions.menus.destroy',
],
'admin.menu_items' => [
'index' => 'menu::permissions.menu_items.index',
'create' => 'menu::permissions.menu_items.create',
'edit' => 'menu::permissions.menu_items.edit',
'destroy' => 'menu::permissions.menu_items.destroy',
],
];

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMenusTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('menus', function (Blueprint $table) {
$table->increments('id');
$table->boolean('is_active');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('menus');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMenuTranslationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('menu_translations', function (Blueprint $table) {
$table->increments('id');
$table->integer('menu_id')->unsigned();
$table->string('locale');
$table->string('name');
$table->unique(['menu_id', 'locale']);
$table->foreign('menu_id')->references('id')->on('menus')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('menu_translations');
}
}

View File

@@ -0,0 +1,47 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMenuitemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('menu_items', function (Blueprint $table) {
$table->increments('id');
$table->integer('menu_id')->unsigned()->index();
$table->integer('parent_id')->unsigned()->nullable();
$table->integer('category_id')->unsigned()->nullable();
$table->integer('page_id')->unsigned()->nullable();
$table->string('type');
$table->string('url')->nullable();
$table->string('target');
$table->integer('position')->unsigned()->nullable();
$table->boolean('is_root')->default(false);
$table->boolean('is_fluid');
$table->boolean('is_active');
$table->timestamps();
$table->foreign('menu_id')->references('id')->on('menus')->onDelete('cascade');
$table->foreign('parent_id')->references('id')->on('menu_items')->onDelete('cascade');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->foreign('page_id')->references('id')->on('pages')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('menu_items');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateMenuitemTranslationTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('menu_item_translations', function (Blueprint $table) {
$table->increments('id');
$table->integer('menu_item_id')->unsigned();
$table->string('locale');
$table->string('name');
$table->unique(['menu_item_id', 'locale']);
$table->foreign('menu_item_id')->references('id')->on('menu_items')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('menu_item_translations');
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddIconColumnToMenuItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('menu_items', function (Blueprint $table) {
$table->string('icon')->nullable()->after('url');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('menu_items', function (Blueprint $table) {
$table->dropColumn('icon');
});
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\Menu\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class MenuDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Modules\Menu\Entities;
use Modules\Admin\Ui\AdminTable;
use Modules\Support\Eloquent\Model;
use Modules\Support\Eloquent\Translatable;
class Menu extends Model
{
use Translatable;
/**
* The relations to eager load on every query.
*
* @var array
*/
protected $with = ['translations'];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['is_active'];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'is_active' => 'boolean',
];
/**
* The attributes that are translatable.
*
* @var array
*/
protected $translatedAttributes = ['name'];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::addActiveGlobalScope();
}
public static function for($menuId)
{
return static::findOrNew($menuId)
->menuItems()
->with(['category', 'page', 'files'])
->get()
->noCleaning()
->nest();
}
public function menuItems()
{
return $this->hasMany(MenuItem::class)->orderByRaw('-position DESC');
}
/**
* Get table data for the resource
*
* @return \Illuminate\Http\JsonResponse
*/
public function table()
{
return new AdminTable($this->newQuery()->withoutGlobalScope('active'));
}
}

View File

@@ -0,0 +1,232 @@
<?php
namespace Modules\Menu\Entities;
use TypiCMS\NestableTrait;
use Modules\Page\Entities\Page;
use Modules\Media\Entities\File;
use Modules\Support\Eloquent\Model;
use Modules\Media\Eloquent\HasMedia;
use Modules\Category\Entities\Category;
use Modules\Support\Eloquent\Translatable;
class MenuItem extends Model
{
use Translatable, NestableTrait, HasMedia;
/**
* The relations to eager load on every query.
*
* @var array
*/
protected $with = ['translations'];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'menu_id',
'category_id',
'page_id',
'parent_id',
'type',
'url',
'icon',
'target',
'position',
'is_root',
'is_fluid',
'is_active',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'is_root' => 'boolean',
'is_fluid' => 'boolean',
'is_active' => 'boolean',
];
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['background_image'];
/**
* The attributes that are translatable.
*
* @var array
*/
protected $translatedAttributes = ['name'];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::addGlobalScope('not_root', function ($query) {
$query->where('is_root', false);
});
static::addActiveGlobalScope();
}
public function menu()
{
return $this->belongsTo(Menu::class);
}
public function children()
{
return $this->hasMany(MenuItem::class, 'parent_id');
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function page()
{
return $this->belongsTo(Page::class);
}
/**
* Set the menu item's page id.
*
* @param int $pageId
* @return void
*/
public function setPageIdAttribute($pageId)
{
$this->attributes['page_id'] = $pageId;
}
/**
* Set the menu item's parent id.
*
* @param int $parentId
* @return void
*/
public function setParentIdAttribute($parentId)
{
$this->attributes['parent_id'] = $parentId;
}
/**
* Get the menu item's background image.
*
* @return \Modules\Media\Entities\File
*/
public function getBackgroundImageAttribute()
{
return $this->files->where('pivot.zone', 'background_image')->first() ?: new File;
}
/**
* Determine if the menu item has any children.
*
* @return bool
*/
public function hasChildren()
{
return $this->items->isNotEmpty();
}
/**
* Determine if the menu item type is category.
*
* @return bool
*/
public function isCategoryType()
{
return $this->type === 'category';
}
/**
* Determine if the menu item type is page.
*
* @return bool
*/
public function isPageType()
{
return $this->type === 'page';
}
/**
* Determine if the menu item type is url.
*
* @return bool
*/
public function isUrlType()
{
return $this->type === 'url';
}
/**
* Returns the public url for the menu item.
*
* @return string
*/
public function url()
{
if ($this->isCategoryType()) {
return optional($this->category)->url();
}
if ($this->isPageType()) {
return optional($this->page)->url();
}
if ($this->getAttributeFromArray('url') === '#') {
return '#';
}
if (filter_var($this->getAttributeFromArray('url'), FILTER_VALIDATE_URL)) {
return $this->getAttributeFromArray('url');
}
return localized_url(locale(), $this->getAttributeFromArray('url'));
}
/**
* Get the root menu item for the given menu id.
*
* @param int $menuId
* @return $this
*/
public static function root($menuId)
{
return static::withoutGlobalScope('not_root')
->where('menu_id', $menuId)
->firstOrFail();
}
/**
* Get the parents of the given menuId.
*
* @param int $menuId
* @param int $menuItemId
* @return array
*/
public static function parents($menuId, $menuItemId)
{
return static::withoutGlobalScope('active')
->where('id', '!=', $menuItemId)
->where('menu_id', $menuId)
->get()
->noCleaning()
->nest()
->setIndent('¦–– ')
->listsFlattened('name');
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Modules\Menu\Entities;
use Modules\Support\Eloquent\TranslationModel;
class MenuItemTranslation extends TranslationModel
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name'];
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Modules\Menu\Entities;
use Modules\Support\Eloquent\TranslationModel;
class MenuTranslation extends TranslationModel
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name'];
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Modules\Menu\Events\Listeners;
use Modules\Menu\Entities\Menu;
use Modules\Menu\Entities\MenuItem;
class CreateRootMenuItem
{
/**
* Handle the event.
*
* @param \Modules\Menu\Entities\Menu $menu
* @return void
*/
public function handle(Menu $menu)
{
$data = [
'menu_id' => $menu->id,
'type' => 'URL',
'target' => '_self',
'position' => 0,
'is_root' => true,
'is_fluid' => false,
'is_active' => true,
];
foreach (supported_locales() as $locale => $language) {
$data[$locale]['name'] = 'root';
}
MenuItem::create($data);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Modules\Menu\Http\Controllers\Admin;
use Modules\Menu\Entities\Menu;
use Modules\Admin\Traits\HasCrudActions;
use Modules\Menu\Http\Requests\SaveMenuRequest;
class MenuController
{
use HasCrudActions;
/**
* Model for the resource.
*
* @var string
*/
protected $model = Menu::class;
/**
* Label of the resource.
*
* @var string
*/
protected $label = 'menu::menus.menu';
/**
* View path of the resource.
*
* @var string
*/
protected $viewPath = 'menu::admin.menus';
/**
* Form requests for the resource.
*
* @var array|string
*/
protected $validation = SaveMenuRequest::class;
/**
* Redirect to url after saving a resource.
*
* @param \Modules\Menu\Entities\Menu $menu
* @return \Illuminate\Http\Response
*/
protected function redirectTo($menu)
{
return redirect()->route('admin.menus.edit', $menu);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$menu = Menu::withoutGlobalScope('active')->findOrFail($id);
$menuItems = $menu->menuItems()
->withoutGlobalScope('active')
->withoutGlobalScope('not_root')
->get()
->nest();
return view('menu::admin.menus.edit', compact('menu', 'menuItems'));
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Modules\Menu\Http\Controllers\Admin;
use Modules\Menu\Entities\Menu;
use Modules\Menu\Entities\MenuItem;
use Modules\Admin\Ui\Facades\TabManager;
use Modules\Menu\Http\Requests\SaveMenuItemRequest;
class MenuItemController
{
/**
* Show the form for creating a new resource.
*
* @param int $menuId
* @return \Illuminate\Http\Response
*/
public function create($menuId)
{
$menuItem = new MenuItem;
$tabs = TabManager::get('menu_items');
return view('menu::admin.menu_items.create', compact('menuId', 'menuItem', 'tabs'));
}
/**
* Store a newly created resource in storage.
*
* @param int $menuId
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store($menuId, SaveMenuItemRequest $request)
{
$menuItem = MenuItem::create(
$this->prepare($menuId, $request->all())
);
return redirect()->route('admin.menus.edit', $menuId)
->withSuccess(trans('admin::messages.resource_saved', ['resource' => trans('menu::menu_items.menu_item')]));
}
/**
* Show the form for editing the specified resource.
*
* @param int $menuId
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($menuId, $id)
{
$menuItem = MenuItem::withoutGlobalScope('active')->findOrFail($id);
$tabs = TabManager::get('menu_items');
return view('menu::admin.menu_items.edit', compact('menuId', 'menuItem', 'tabs'));
}
/**
* Update the specified resource in storage.
*
* @param int $menuId
* @param int $id
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function update($menuId, $id, SaveMenuItemRequest $request)
{
MenuItem::withoutGlobalScope('active')
->findOrFail($id)
->update(
$this->prepare($menuId, $request->all())
);
return redirect()->route('admin.menus.edit', $menuId)
->withSuccess(trans('admin::messages.resource_saved', ['resource' => trans('menu::menu_items.menu_item')]));
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return void
*/
public function destroy($id)
{
MenuItem::withoutGlobalScope('active')->findOrFail($id)->delete();
}
/**
* Prepare menu item attributes for saving.
*
* @param int $menuId
* @param array $attributes
* @return array
*/
private function prepare($menuId, array $attributes)
{
if (is_null($attributes['parent_id'])) {
$attributes['parent_id'] = $this->parentId($menuId);
}
return array_merge($attributes, ['menu_id' => $menuId]);
}
/**
* Get parent id for the given menu id.
*
* @param string string
* @return int
*/
private function parentId($menuId)
{
return MenuItem::withoutGlobalScope('not_root')
->where('menu_id', $menuId)
->value('id');
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\Menu\Http\Controllers\Admin;
use Modules\Menu\Services\MenuItemOrdener;
class MenuItemOrderController
{
/**
* Update the specified resource in storage.
*
* @param \Modules\Menu\Services\MenuItemOrdener $ordener
* @return void
*/
public function update(MenuItemOrdener $ordener)
{
$ordener->order(request()->json()->all());
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\Menu\Http\Requests;
use Illuminate\Validation\Rule;
use Modules\Core\Http\Requests\Request;
class SaveMenuItemRequest extends Request
{
/**
* Available attributes.
*
* @var string
*/
protected $availableAttributes = 'menu::attributes';
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'type' => ['required', Rule::in('category', 'page', 'url')],
'category_id' => 'required_if:type,category',
'page_id' => 'required_if:type,page',
'url' => 'required_if:type,url',
'target' => ['required', Rule::in('_self', '_blank')],
'is_fluid' => 'required|boolean',
'is_active' => 'required|boolean',
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\Menu\Http\Requests;
use Modules\Core\Http\Requests\Request;
class SaveMenuRequest extends Request
{
/**
* Available attributes.
*
* @var array
*/
protected $availableAttributes = 'menu::attributes';
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Modules\Menu\MegaMenu;
use Illuminate\Support\Facades\Cache;
use Modules\Menu\Entities\Menu as MenuModel;
class MegaMenu
{
private $menuId;
public function __construct($menuId)
{
$this->menuId = $menuId;
}
public function menus()
{
return Cache::tags(['mega_menu', 'menu_items', 'pages', 'categories'])
->rememberForever(md5("mega_menu.{$this->menuId}:" . locale()), function () {
return $this->getMenus()->map(function ($menu) {
return new Menu($menu);
});
});
}
private function getMenus()
{
return MenuModel::for($this->menuId)->where('menu_id', $this->menuId);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Modules\Menu\MegaMenu;
use Modules\Category\Entities\Category;
class Menu
{
private $menu;
private $subMenus;
public function __construct($menu)
{
$this->menu = $menu;
}
public function isFluid()
{
return $this->menu->is_fluid;
}
public function url()
{
return $this->menu->url();
}
public function hasIcon()
{
return ! is_null($this->icon());
}
public function icon()
{
return $this->menu->icon;
}
public function hasBackgroundImage()
{
return ! is_null($this->backgroundImage());
}
public function backgroundImage()
{
return $this->menu->background_image->path;
}
public function target()
{
return $this->menu->target;
}
public function name()
{
return $this->menu->name;
}
public function hasSubMenus()
{
return $this->subMenus()->isNotEmpty();
}
public function subMenus()
{
if (! is_null($this->subMenus)) {
return $this->subMenus;
}
return $this->subMenus = $this->getSubMenus()->map(function ($subMenu) {
return new SubMenu($subMenu);
});
}
private function getSubMenus()
{
if ($this->menu->isCategoryType()) {
return $this->getCategorySubMenus();
}
return $this->menu->items ?? collect();
}
private function getCategorySubMenus()
{
return Category::tree()
->where('id', $this->menu->category_id)
->first()
->items ?? collect();
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Modules\Menu\MegaMenu;
use Modules\Category\Entities\Category;
class SubMenu
{
private $subMenu;
private $subMenuItems;
public function __construct($subMenu)
{
$this->subMenu = $subMenu;
}
public function url()
{
return $this->subMenu->url();
}
public function target()
{
if ($this->subMenu instanceof Category) {
return '_self';
}
return $this->subMenu->target;
}
public function name()
{
return $this->subMenu->name;
}
public function hasItems()
{
return $this->items()->isNotEmpty();
}
public function items()
{
if (! is_null($this->subMenuItems)) {
return $this->subMenuItems;
}
return $this->subMenuItems = $this->subMenu->items->map(function ($item) {
return new SubMenu($item);
});
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Modules\Menu\Providers;
use Modules\Menu\Entities\Menu;
use Modules\Menu\Events\Listeners\CreateRootMenuItem;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
parent::boot();
Menu::created(CreateRootMenuItem::class);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Modules\Menu\Providers;
use Modules\Menu\Admin\MenuItemTabs;
use Modules\Support\Traits\AddsAsset;
use Illuminate\Support\ServiceProvider;
use Modules\Admin\Ui\Facades\TabManager;
class MenuServiceProvider extends ServiceProvider
{
use AddsAsset;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
TabManager::register('menu_items', MenuItemTabs::class);
$this->addAdminAssets('admin.menus.(create|edit)', ['admin.menu.css', 'admin.menu.js']);
$this->addAdminAssets('admin.menus.items.(create|edit)', ['admin.media.css', 'admin.media.js', 'admin.menu.js']);
}
}

View File

@@ -0,0 +1,53 @@
import 'nestable2';
window.admin.removeSubmitButtonOffsetOn('#image');
$('#type').on('change', (e) => {
$('.link-field').addClass('hide');
$(`.${e.currentTarget.value}-field`).removeClass('hide');
});
$('.dd').nestable({ maxDepth: 15 });
$('.dd').on('change', () => {
$.ajax({
type: 'PUT',
url: route('admin.menus.items.order.update'),
contentType: 'application/json; charset=utf-8',
data: JSON.stringify($('.dd').nestable('serialize')[0]),
success() {
success(trans('menu::messages.menu_items_order_updated'));
},
error(xhr) {
error(xhr.responseJSON.message);
},
});
});
let id;
let confirmationModal = $('#confirmation-modal');
$('.delete-menu-item').on('click', (e) => {
id = $(e.currentTarget).closest('.dd-item').data('id');
confirmationModal.modal('show');
});
confirmationModal.find('form').on('submit', (e) => {
e.preventDefault();
confirmationModal.modal('hide');
$.ajax({
type: 'DELETE',
url: route('admin.menus.items.destroy', id),
success() {
success(trans('menu::messages.menu_item_deleted'));
$(`.dd-item[data-id="${id}"]`).fadeOut();
},
error(xhr) {
error(xhr.responseJSON.message);
},
});
});

View File

@@ -0,0 +1,205 @@
#menu-create-form .box-body,
#menu-edit-form .box-body {
padding-bottom: 5px;
}
#menu-edit-form .box-body {
.dd {
padding-bottom: 15px;
}
}
.menu-item-actions {
float: left;
display: inline-block;
margin-top: 3px;
margin-left: 6px;
}
.menu-item-actions .btn:last-child {
margin-right: 10px;
}
.delete-menu-item {
float: left;
}
.edit-menu-item {
float: left;
}
.dd {
position: relative;
display: block;
margin: 0;
padding: 0;
list-style: none;
font-size: 13px;
line-height: 29px;
}
.dd-list {
display: block;
position: relative;
margin: 0;
padding: 0;
list-style: none;
.dd-list {
padding-left: 25px;
clear: both;
}
.btn {
background: transparent;
color: #555555;
padding: 6px;
}
.dd-expand,
.dd-collapse {
margin-top: 10px;
color: #555555;
&:focus {
outline: 0;
}
}
}
.dd-collapsed .dd-list {
display: none;
}
.dd-item,
.dd-item-root,
.dd-empty,
.dd-placeholder {
display: block;
position: relative;
margin: 0;
padding: 0;
min-height: 20px;
font-size: 13px;
line-height: 20px;
}
.dd-handle-root,
.dd-handle {
font-family: "Roboto", sans-serif;
font-weight: 400;
font-size: 15px;
display: block;
margin: 5px 0;
padding: 8px 10px;
color: #333;
text-decoration: none;
border: 1px solid #d9d9d9;
background: #fafafa;
-webkit-border-radius: 3px;
border-radius: 3px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.dd-handle:hover {
color: #333;
background: #fff;
cursor: move;
}
.dd-item-root > .btn {
display: none;
}
.dd-item-root > button,
.dd-item > button {
display: block;
position: relative;
cursor: pointer;
float: right;
width: 25px;
height: 20px;
margin: 5px 0;
padding: 0;
text-indent: 100%;
white-space: nowrap;
overflow: hidden;
border: 0;
background: transparent;
font-size: 12px;
line-height: 1;
text-align: center;
font-weight: bold;
}
.dd-item-root > button:before,
.dd-item > button:before {
content: '+';
display: block;
position: absolute;
width: 100%;
text-align: center;
text-indent: 0;
font-size: 20px;
line-height: 9px;
}
.dd-item-root > button[data-action="collapse"]:before,
.dd-item > button[data-action="collapse"]:before {
content: '-';
font-size: 20px;
line-height: 9px;
}
.dd-placeholder,
.dd-empty {
margin: 5px 0;
padding: 0;
min-height: 30px !important;
background: #f2fbff !important;
border: 1px dashed #b6bcbf;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.dd-empty {
border: 1px dashed #bbb;
min-height: 100px;
background-color: #e5e5e5;
background-image: -webkit-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), -webkit-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);
background-image: -moz-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), -moz-linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);
background-image: linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff), linear-gradient(45deg, #ffffff 25%, transparent 25%, transparent 75%, #ffffff 75%, #ffffff);
background-size: 60px 60px;
background-position: 0 0, 30px 30px;
}
.dd-dragel {
position: absolute;
pointer-events: none;
z-index: 9999;
}
.dd-dragel > .dd-item .dd-handle {
margin-top: 0;
}
.dd-dragel .dd-handle {
-webkit-box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1);
box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, 0.1);
}
.dd-hover > .dd-handle {
background: #2196f3 !important;
}
@media only screen and (min-width: 700px) {
.dd {
float: left;
width: 100%;
+ .dd {
margin-left: 2%;
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
return [
'name' => 'Name',
'type' => 'Type',
'url' => 'URL',
'icon' => 'Icon',
'category_id' => 'Category',
'page_id' => 'Page',
'parent_id' => 'Parent Menu Item',
'target' => 'Target',
'is_fluid' => 'Fluid Menu',
'is_active' => 'Status',
];

View File

@@ -0,0 +1,30 @@
<?php
return [
'menu_item' => 'Menu Item',
'back_to_menu_edit_page' => 'Back to Menu Edit Page',
'tabs' => [
'group' => [
'menu_item_information' => 'Menu Item Information',
],
'general' => 'General',
'image' => 'Image',
],
'form' => [
'types' => [
'category' => 'Category',
'page' => 'Page',
'url' => 'URL',
],
'targets' => [
'_self' => 'Same Tab',
'_blank' => 'New Tab',
],
'select_category' => 'Select Category',
'select_page' => 'Select Page',
'select_parent' => 'Select Parent',
'background_image' => 'Background Image',
'full_width_menu' => 'This is a full width menu',
'enable_the_menu_item' => 'Enable the menu item',
],
];

View File

@@ -0,0 +1,13 @@
<?php
return [
'menu' => 'Menu',
'menus' => 'Menus',
'table' => [
'name' => 'Name',
],
'form' => [
'please_save_the_menu_first' => 'Please save the menu first to add menu items.',
'enable_the_menu' => 'Enable the menu',
],
];

View File

@@ -0,0 +1,6 @@
<?php
return [
'menu_item_deleted' => 'Menu item has been deleted.',
'menu_items_order_updated' => 'Menu items order has been updated.',
];

View File

@@ -0,0 +1,16 @@
<?php
return [
'menus' => [
'index' => 'Index Menus',
'create' => 'Create Menus',
'edit' => 'Edit Menus',
'destroy' => 'Delete Menus',
],
'menu_items' => [
'index' => 'Index Menu Items',
'create' => 'Create Menu Items',
'edit' => 'Edit Menu Items',
'destroy' => 'Delete Menu Items',
],
];

View File

@@ -0,0 +1,5 @@
<?php
return [
'menus' => 'Menus',
];

View File

@@ -0,0 +1,19 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('admin::resource.create', ['resource' => trans('menu::menu_items.menu_item')]))
<li><a href="{{ route('admin.menus.index') }}">{{ trans('menu::menus.menus') }}</a></li>
<li><a href="{{ route('admin.menus.edit', $menuId) }}">{{ trans('admin::resource.edit', ['resource' => trans('menu::menus.menu')]) }}</a></li>
<li class="active">{{ trans('admin::resource.create', ['resource' => trans('menu::menu_items.menu_item')]) }}</li>
@endcomponent
@section('content')
<form method="POST" action="{{ route('admin.menus.items.store', $menuId) }}" class="form-horizontal" id="menu-item-create-form" novalidate>
{{ csrf_field() }}
{!! $tabs->render(compact('menuId', 'menuItem')) !!}
</form>
@endsection
@include('menu::admin.menu_items.partials.shortcuts')

View File

@@ -0,0 +1,20 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('admin::resource.edit', ['resource' => trans('menu::menu_items.menu_item')]))
@slot('subtitle', $menuItem->title)
<li><a href="{{ route('admin.menus.edit', $menuId) }}">{{ trans('admin::resource.edit', ['resource' => trans('menu::menus.menu')]) }}</a></li>
<li class="active">{{ trans('admin::resource.edit', ['resource' => trans('menu::menu_items.menu_item')]) }}</li>
@endcomponent
@section('content')
<form method="POST" action="{{ route('admin.menus.items.update', [$menuId, $menuItem]) }}" class="form-horizontal" id="menu-item-edit-form" novalidate>
{{ csrf_field() }}
{{ method_field('put') }}
{!! $tabs->render(compact('menuId', 'menuItem')) !!}
</form>
@endsection
@include('menu::admin.menu_items.partials.shortcuts')

View File

@@ -0,0 +1,14 @@
@push('shortcuts')
<dl class="dl-horizontal">
<dt><code>b</code></dt>
<dd>{{ trans('menu::menu_items.back_to_menu_edit_page') }}</dd>
</dl>
@endpush
@push('scripts')
<script>
keypressAction([
{ key: 'b', route: "{{ route('admin.menus.edit', $menuId) }}" },
]);
</script>
@endpush

View File

@@ -0,0 +1,24 @@
<div class="row">
<div class="col-md-8">
{{ Form::text('name', trans('menu::attributes.name'), $errors, $menuItem, ['required' => true]) }}
{{ Form::select('type', trans('menu::attributes.type'), $errors, trans('menu::menu_items.form.types'), $menuItem, ['required' => true]) }}
<div class="link-field category-field {{ old('type', $menuItem->type ?? 'category') !== 'category' ? 'hide' :'' }}">
{{ Form::select('category_id', trans('menu::attributes.category_id'), $errors, $categories, $menuItem, ['required' => true]) }}
</div>
<div class="link-field page-field {{ old('type', $menuItem->type) !== 'page' ? 'hide' :'' }}">
{{ Form::select('page_id', trans('menu::attributes.page_id'), $errors, $pages, $menuItem, ['required' => true]) }}
</div>
<div class="link-field url-field {{ old('type', $menuItem->type) !== 'url' ? 'hide' :'' }}">
{{ Form::text('url', trans('menu::attributes.url'), $errors, $menuItem, ['required' => true]) }}
</div>
{{ Form::text('icon', trans('menu::attributes.icon'), $errors, $menuItem) }}
{{ Form::checkbox('is_fluid', trans('menu::attributes.is_fluid'), trans('menu::menu_items.form.full_width_menu'), $errors, $menuItem) }}
{{ Form::select('target', trans('menu::attributes.target'), $errors, trans('menu::menu_items.form.targets'), $menuItem) }}
{{ Form::select('parent_id', trans('menu::attributes.parent_id'), $errors, $parentMenuItems, $menuItem) }}
{{ Form::checkbox('is_active', trans('menu::attributes.is_active'), trans('menu::menu_items.form.enable_the_menu_item'), $errors, $menuItem) }}
</div>
</div>

View File

@@ -0,0 +1,5 @@
@include('media::admin.image_picker.single', [
'title' => trans('menu::menu_items.form.background_image'),
'inputName' => 'files[background_image]',
'file' => $menuItem->backgroundImage,
])

View File

@@ -0,0 +1,18 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('admin::resource.create', ['resource' => trans('menu::menus.menu')]))
<li><a href="{{ route('admin.menus.index') }}">{{ trans('menu::menus.menus') }}</a></li>
<li class="active">{{ trans('admin::resource.create', ['resource' => trans('menu::menus.menu')]) }}</li>
@endcomponent
@section('content')
<form method="POST" action="{{ route('admin.menus.store') }}" class="form-horizontal" id="menu-create-form" novalidate>
{{ csrf_field() }}
@include('menu::admin.menus.form.fields')
</form>
@endsection
@include('menu::admin.menus.partials.shortcuts')

View File

@@ -0,0 +1,27 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('admin::resource.edit', ['resource' => trans('menu::menus.menu')]))
@slot('subtitle', $menu->title)
<li><a href="{{ route('admin.menus.index') }}">{{ trans('menu::menus.menus') }}</a></li>
<li class="active">{{ trans('admin::resource.edit', ['resource' => trans('menu::menus.menu')]) }}</li>
@endcomponent
@section('content')
<form method="POST" action="{{ route('admin.menus.update', $menu) }}" class="form-horizontal" id="menu-edit-form" novalidate>
{{ csrf_field() }}
{{ method_field('put') }}
@include('menu::admin.menus.form.fields')
</form>
@endsection
@push('globals')
<script>
FleetCart.langs['menu::messages.menu_item_deleted'] = '{{ trans('menu::messages.menu_item_deleted') }}';
FleetCart.langs['menu::messages.menu_items_order_updated'] = '{{ trans('menu::messages.menu_items_order_updated') }}';
</script>
@endpush
@include('menu::admin.menus.partials.shortcuts')

View File

@@ -0,0 +1,46 @@
<div class="row">
@hasAccess('admin.menu_items.index')
<div class="col-md-6">
@if ($menu->exists)
@hasAccess('admin.menu_items.create')
<div class="btn-group pull-right m-b-15">
<a href="{{ route('admin.menus.items.create', $menu) }}" class="btn btn-primary">
{{ trans('admin::resource.create', ['resource' => trans('menu::menu_items.menu_item')]) }}
</a>
</div>
@endHasAccess
@hasAccess('admin.menu_items.edit')
<div class="box box-primary overflow-hidden">
<div class="box-body">
<div class="dd">
@include('menu::admin.menus.form.menu_items_list')
</div>
</div>
</div>
@endHasAccess
@else
<div class="alert alert-info">
{{ trans('menu::menus.form.please_save_the_menu_first') }}
</div>
@endif
</div>
@endHasAccess
<div class="col-md-6">
<div class="box box-primary">
<div class="box-body">
{{ Form::text('name', trans('menu::attributes.name'), $errors, $menu, ['required' => true]) }}
{{ Form::checkbox('is_active', trans('menu::attributes.is_active'), trans('menu::menus.form.enable_the_menu'), $errors, $menu) }}
<div class="form-group">
<div class="col-md-offset-3 col-md-9">
<button type="submit" class="btn btn-primary" data-loading>
{{ trans('admin::admin.buttons.save') }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,23 @@
<ol class="dd-list">
@foreach ($menuItems as $menuItem)
<li class="dd-item" data-id="{{ $menuItem->id }}">
@if (! $menuItem->is_root)
<div class="menu-item-actions btn-group" role="group">
<a href="{{ route('admin.menus.items.edit', [$menu->id, $menuItem->id]) }}" class="btn edit-menu-item ">
<i class="fa fa-pencil"></i>
</a>
<button type="button" class="btn delete-menu-item" data-action="{{ route('admin.menus.items.destroy', [$menu->id, $menuItem->id]) }}">
<i class="fa fa-times"></i>
</button>
</div>
@endif
<div class="{{ $menuItem->is_root ? 'dd-handle-root' : 'dd-handle' }}">{{ $menuItem->name }}</div>
@if (count($menuItem->items) !== 0)
@include('menu::admin.menus.form.menu_items_list', ['menuItems' => $menuItem->items])
@endif
</li>
@endforeach
</ol>

View File

@@ -0,0 +1,49 @@
@extends('admin::layout')
@component('admin::components.page.header')
@slot('title', trans('menu::menus.menus'))
<li class="active">{{ trans('menu::menus.menu') }}</li>
@endcomponent
@component('admin::components.page.index_table')
@slot('buttons', ['create'])
@slot('resource', 'menus')
@slot('name', trans('menu::menus.menu'))
@slot('thead')
<tr>
@include('admin::partials.table.select_all')
<th>{{ trans('admin::admin.table.id') }}</th>
<th>{{ trans('menu::menus.table.name') }}</th>
<th>{{ trans('admin::admin.table.status') }}</th>
<th data-sort>{{ trans('admin::admin.table.created') }}</th>
</tr>
@endslot
@endcomponent
@push('shortcuts')
<dl class="dl-horizontal">
<dt><code>c</code></dt>
<dd>{{ trans('admin::resource.create', ['resource' => trans('menu::menus.menu')]) }}</dd>
</dl>
@endpush
@push('scripts')
<script>
keypressAction([
{ key: 'c', route: "{{ route('admin.menus.create') }}" },
]);
new DataTable('#menus-table .table', {
columns: [
{ data: 'checkbox', orderable: false, searchable: false, width: '3%' },
{ data: 'id', width: '5%' },
{ data: 'name', name: 'translations.name', orderable: false, defaultContent: '' },
{ data: 'status', name: 'is_active', searchable: false },
{ data: 'created', name: 'created_at' },
]
});
</script>
@endpush

View File

@@ -0,0 +1,25 @@
@push('shortcuts')
@isset($menu)
<dl class="dl-horizontal">
<dt><code>c</code></dt>
<dd>{{ trans('admin::resource.create', ['resource' => trans('menu::menu_items.menu_item')]) }}</dd>
</dl>
@endisset
<dl class="dl-horizontal">
<dt><code>b</code></dt>
<dd>{{ trans('admin::admin.shortcuts.back_to_index', ['name' => trans('menu::menus.menu')]) }}</dd>
</dl>
@endpush
@push('scripts')
<script>
keypressAction([
@isset($menu)
{ key: 'c', route: "{{ route('admin.menus.items.create', $menu) }}" },
@endisset
{ key: 'b', route: "{{ route('admin.menus.index') }}" },
]);
</script>
@endpush

View File

@@ -0,0 +1,75 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('menus', [
'as' => 'admin.menus.index',
'uses' => 'MenuController@index',
'middleware' => 'can:admin.menus.index',
]);
Route::get('menus/create', [
'as' => 'admin.menus.create',
'uses' => 'MenuController@create',
'middleware' => 'can:admin.menus.create',
]);
Route::post('menus', [
'as' => 'admin.menus.store',
'uses' => 'MenuController@store',
'middleware' => 'can:admin.menus.create',
]);
Route::get('menus/{id}/edit', [
'as' => 'admin.menus.edit',
'uses' => 'MenuController@edit',
'middleware' => 'can:admin.menus.edit',
]);
Route::put('menus/{id}', [
'as' => 'admin.menus.update',
'uses' => 'MenuController@update',
'middleware' => 'can:admin.menus.edit',
]);
Route::delete('menus/{ids?}', [
'as' => 'admin.menus.destroy',
'uses' => 'MenuController@destroy',
'middleware' => 'can:admin.menus.destroy',
]);
Route::get('menus/{menuId?}/items/create', [
'as' => 'admin.menus.items.create',
'uses' => 'MenuItemController@create',
'middleware' => 'can:admin.menu_items.create',
]);
Route::post('menus/{menuId}/items', [
'as' => 'admin.menus.items.store',
'uses' => 'MenuItemController@store',
'middleware' => 'can:admin.menu_items.create',
]);
Route::get('menus/{menuId}/items/{id}/edit', [
'as' => 'admin.menus.items.edit',
'uses' => 'MenuItemController@edit',
'middleware' => 'can:admin.menu_items.edit',
]);
Route::put('menus/{menuId}/items/{id}', [
'as' => 'admin.menus.items.update',
'uses' => 'MenuItemController@update',
'middleware' => 'can:admin.menu_items.edit',
]);
Route::delete('menus/items/{id}', [
'as' => 'admin.menus.items.destroy',
'uses' => 'MenuItemController@destroy',
'middleware' => 'can:admin.menu_items.destroy',
]);
Route::put('menus/items/order', [
'as' => 'admin.menus.items.order.update',
'uses' => 'MenuItemOrderController@update',
'middleware' => 'can:admin.menu_items.edit',
]);

View File

@@ -0,0 +1,43 @@
<?php
namespace Modules\Menu\Services;
use Modules\Menu\Entities\MenuItem;
class MenuItemOrdener
{
/**
* Order the menu items.
*
* @param array $rootItem
* @return void
*/
public function order(array $rootItem)
{
$this->orderChildren($rootItem['id'], $rootItem['children']);
}
/**
* Order child menu items recursively.
*
* @param int $parentId
* @param array $childItems
* @return void
*/
private function orderChildren($parentId, array $childItems)
{
foreach ($childItems as $position => $childItem) {
$menuItem = MenuItem::find($childItem['id']);
if (is_null($menuItem)) {
continue;
}
$menuItem->update(['parent_id' => $parentId, 'position' => $position]);
if (isset($childItem['children'])) {
$this->orderChildren($childItem['id'], $childItem['children']);
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Modules\Menu\Sidebar;
use Maatwebsite\Sidebar\Item;
use Maatwebsite\Sidebar\Menu;
use Maatwebsite\Sidebar\Group;
use Modules\Admin\Sidebar\BaseSidebarExtender;
class SidebarExtender extends BaseSidebarExtender
{
public function extend(Menu $menu)
{
$menu->group(trans('admin::sidebar.content'), function (Group $group) {
$group->item(trans('menu::sidebar.menus'), function (Item $item) {
$item->weight(35);
$item->icon('fa fa-bars');
$item->route('admin.menus.index');
$item->authorize(
$this->auth->hasAccess('admin.menus.index')
);
});
});
}
}

View File

@@ -0,0 +1,28 @@
{
"name": "fleetcart/menu",
"description": "The FleetCart Menu Module.",
"authors": [
{
"name": "Envay Soft",
"email": "envaysoft@gmail.com"
}
],
"require": {
"php": "^8.0.2",
"typicms/nestablecollection": "^1.1"
},
"autoload": {
"psr-4": {
"Modules\\Menu\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

10
Modules/Menu/module.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "Menu",
"alias": "menu",
"description": "Managing menus.",
"priority": 90,
"providers": [
"Modules\\Menu\\Providers\\MenuServiceProvider",
"Modules\\Menu\\Providers\\EventServiceProvider"
]
}

View File

@@ -0,0 +1,7 @@
{
"name": "menu-module",
"private": true,
"devDependencies": {
"nestable2": "^1.6.0"
}
}

View File

@@ -0,0 +1,8 @@
let mix = require('laravel-mix');
let execSync = require('child_process').execSync;
mix.js(`${__dirname}/Resources/assets/admin/js/main.js`, `${__dirname}/Assets/admin/js/menu.js`)
.sass(`${__dirname}/Resources/assets/admin/sass/main.scss`, `${__dirname}/Assets/admin/css/menu.css`)
.then(() => {
execSync(`npm run rtlcss ${__dirname}/Assets/admin/css/menu.css ${__dirname}/Assets/admin/css/menu.rtl.css`);
});

13
Modules/Menu/yarn.lock Normal file
View File

@@ -0,0 +1,13 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
jquery@>=1.7.2:
version "3.2.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
nestable2@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/nestable2/-/nestable2-1.6.0.tgz#83ffe2483cca1c63771ec0012693b5155bfdd115"
dependencies:
jquery ">=1.7.2"