├── resources ├── views │ ├── .gitkeep │ ├── components │ │ ├── home.blade.php │ │ ├── footer.blade.php │ │ ├── quick.blade.php │ │ ├── filter.blade.php │ │ └── header.blade.php │ └── pages │ │ └── appearance.blade.php ├── svg │ ├── close.svg │ ├── menu.svg │ ├── save.svg │ ├── add.svg │ ├── down.svg │ ├── up.svg │ ├── checked.svg │ ├── home.svg │ ├── types.svg │ ├── edit.svg │ ├── eye.svg │ ├── widgets.svg │ ├── menu-squared.svg │ ├── circled-menu.svg │ ├── move.svg │ ├── areas.svg │ ├── fields.svg │ ├── edit-property.svg │ └── drawing.svg └── lang │ ├── ar │ └── filawidget.php │ ├── en │ └── filawidget.php │ └── fr │ └── filawidget.php ├── CHANGELOG.md ├── screenshots ├── pages.png ├── preview.png ├── widgets.png └── frontend.png ├── src ├── Filawidget.php ├── routes │ └── web.php ├── Observers │ ├── PageObserver.php │ ├── WidgetAreaObserver.php │ └── WidgetTypeObserver.php ├── Resources │ ├── PageResource │ │ └── Pages │ │ │ ├── CreatePage.php │ │ │ ├── EditPage.php │ │ │ └── ListPages.php │ ├── WidgetResource │ │ ├── Pages │ │ │ ├── CreateWidget.php │ │ │ ├── ListWidgets.php │ │ │ └── EditWidget.php │ │ └── Widgets │ │ │ └── WidgetStatsOverview.php │ ├── WidgetTypeResource │ │ └── Pages │ │ │ ├── CreateWidget.php │ │ │ ├── CreateWidgetType.php │ │ │ ├── EditWidget.php │ │ │ ├── EditWidgetType.php │ │ │ ├── ListWidgetTypes.php │ │ │ └── ListWidgets.php │ ├── WidgetAreaResource │ │ ├── Pages │ │ │ ├── CreateWidgetArea.php │ │ │ ├── EditWidgetArea.php │ │ │ └── ListWidgetAreas.php │ │ └── Widgets │ │ │ └── WidgetAreaStatsOverview.php │ ├── WidgetFieldResource │ │ └── Pages │ │ │ ├── CreateWidgetField.php │ │ │ ├── EditWidgetField.php │ │ │ └── ListWidgetFields.php │ ├── WidgetAreaResource.php │ ├── PageResource.php │ ├── WidgetFieldResource.php │ ├── WidgetTypeResource.php │ └── WidgetResource.php ├── Facades │ └── Filawidget.php ├── Commands │ └── FilawidgetCommand.php ├── Widgets │ └── BaseWidget.php ├── Models │ ├── WidgetField.php │ ├── Field.php │ ├── WidgetType.php │ ├── WidgetArea.php │ ├── Widget.php │ └── Page.php ├── Services │ ├── WidgetService.php │ ├── PageService.php │ └── AreaService.php ├── FilawidgetServiceProvider.php ├── FilaWidgetPlugin.php └── Pages │ └── Appearance.php ├── database ├── factories │ └── ModelFactory.php └── migrations │ └── create_filawidget_table.php.stub ├── config └── filawidget.php ├── LICENSE.md ├── composer.json └── README.md /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `filawidget` will be documented in this file. 4 | -------------------------------------------------------------------------------- /screenshots/pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibrahimBougaoua/filawidget/HEAD/screenshots/pages.png -------------------------------------------------------------------------------- /screenshots/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibrahimBougaoua/filawidget/HEAD/screenshots/preview.png -------------------------------------------------------------------------------- /screenshots/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibrahimBougaoua/filawidget/HEAD/screenshots/widgets.png -------------------------------------------------------------------------------- /src/Filawidget.php: -------------------------------------------------------------------------------- 1 | name('widgets.updateOrder'); 8 | -------------------------------------------------------------------------------- /src/Observers/PageObserver.php: -------------------------------------------------------------------------------- 1 | slug = Str::slug($page->title, '-'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Resources/PageResource/Pages/CreatePage.php: -------------------------------------------------------------------------------- 1 | identifier = Str::slug($widgetArea->name, '-'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Resources/WidgetTypeResource/Pages/CreateWidget.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/svg/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Commands/FilawidgetCommand.php: -------------------------------------------------------------------------------- 1 | comment('All done'); 16 | 17 | return self::SUCCESS; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /resources/svg/save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/svg/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Widgets/BaseWidget.php: -------------------------------------------------------------------------------- 1 | config = $config; 12 | } 13 | 14 | public function render() 15 | { 16 | $view = $this->getView(); 17 | 18 | return view($view, $this->config); 19 | } 20 | 21 | abstract protected function getView(); 22 | } 23 | -------------------------------------------------------------------------------- /resources/svg/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/svg/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /config/filawidget.php: -------------------------------------------------------------------------------- 1 | true, 6 | 'should_register_navigation_pages' => true, 7 | 'should_register_navigation_widgets' => true, 8 | 'should_register_navigation_widget_areas' => true, 9 | 'should_register_navigation_fields' => true, 10 | 'should_register_navigation_widget_types' => true, 11 | 'show_home_link' => true, 12 | 'show_quick_appearance' => true, 13 | ]; 14 | -------------------------------------------------------------------------------- /resources/svg/checked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Resources/PageResource/Pages/EditPage.php: -------------------------------------------------------------------------------- 1 | 'array', 23 | ]; 24 | } 25 | -------------------------------------------------------------------------------- /src/Resources/PageResource/Pages/ListPages.php: -------------------------------------------------------------------------------- 1 | get(); 13 | } 14 | 15 | public static function getWidgetBySlug(string $slug): ?Widget 16 | { 17 | return Widget::active()->with(['type', 'area'])->where('slug', $slug)->first(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /resources/svg/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Resources/WidgetTypeResource/Pages/EditWidget.php: -------------------------------------------------------------------------------- 1 | slug = Str::slug($widgetType->name, '-'); 14 | } 15 | 16 | public function updated(WidgetType $widgetType): void 17 | { 18 | Widget::where('widget_type_id', $widgetType->id)->update([ 19 | 'fieldsIds' => $widgetType->fieldsIds, 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Models/Field.php: -------------------------------------------------------------------------------- 1 | 'array', 15 | ]; 16 | 17 | public function types() 18 | { 19 | return $this->belongsToMany(WidgetType::class, 'widget_type_fields', 'field_id', 'widget_type_id'); 20 | } 21 | 22 | public function widgets() 23 | { 24 | return $this->belongsToMany(Widget::class, 'widget_fields', 'widget_field_id', 'widget_id'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/svg/types.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Services/PageService.php: -------------------------------------------------------------------------------- 1 | active()->with('children')->ordered()->get(); 13 | 14 | return $pages->isEmpty() ? collect() : $pages; 15 | } 16 | 17 | public static function getPageBySlug(string $slug): ?Page 18 | { 19 | return Page::active()->with('children')->where('slug', $slug)->first(); 20 | } 21 | 22 | public static function counts() 23 | { 24 | return Page::active()->count(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Services/AreaService.php: -------------------------------------------------------------------------------- 1 | with('widgets')->get(); 13 | } 14 | 15 | public static function getAllAreasWithOrderedWidgets(): ?Collection 16 | { 17 | return WidgetArea::active()->ordered()->withOrderedWidgets()->get(); 18 | } 19 | 20 | public static function getWidgetByIdentifier(string $identifier): ?WidgetArea 21 | { 22 | return WidgetArea::active()->where('identifier', $identifier)->first(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /resources/svg/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/svg/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/components/home.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 |
-------------------------------------------------------------------------------- /src/Resources/WidgetTypeResource/Pages/ListWidgets.php: -------------------------------------------------------------------------------- 1 | url(route('filament.admin.pages.appearance')) 18 | ->icon('heroicon-o-paint-brush') 19 | ->color('success') 20 | ->label(__('filawidget::filawidget.Appearance')), 21 | Actions\CreateAction::make()->icon('heroicon-o-plus'), 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Models/WidgetType.php: -------------------------------------------------------------------------------- 1 | 'array', 18 | 'fieldsIds' => 'array', 19 | ]; 20 | 21 | public function widgets() 22 | { 23 | return $this->hasMany(Widget::class, 'widget_type_id'); 24 | } 25 | 26 | public function fields() 27 | { 28 | return $this->belongsToMany(Field::class, 'widget_type_fields', 'widget_type_id', 'field_id'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Resources/WidgetFieldResource/Pages/ListWidgetFields.php: -------------------------------------------------------------------------------- 1 | url(route('filament.admin.pages.appearance')) 18 | ->icon('heroicon-o-paint-brush') 19 | ->color('success') 20 | ->label(__('filawidget::filawidget.Appearance')), 21 | Actions\CreateAction::make()->icon('heroicon-o-plus'), 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /resources/svg/widgets.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/svg/menu-squared.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/FilawidgetServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('filawidget') 20 | ->hasConfigFile() 21 | ->hasViews() 22 | ->hasMigration('create_filawidget_table') 23 | ->hasCommand(FilawidgetCommand::class); 24 | 25 | if (file_exists($package->basePath('/../resources/views'))) { 26 | $package->hasViews(); 27 | } 28 | 29 | if (file_exists($package->basePath('/../resources/lang'))) { 30 | $package->hasTranslations(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/svg/circled-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Resources/WidgetResource/Pages/ListWidgets.php: -------------------------------------------------------------------------------- 1 | url(route('filament.admin.pages.appearance')) 19 | ->icon('heroicon-o-paint-brush') 20 | ->color('success') 21 | ->label(__('filawidget::filawidget.Appearance')), 22 | Actions\CreateAction::make()->icon('heroicon-o-plus'), 23 | ]; 24 | } 25 | 26 | protected function getHeaderWidgets(): array 27 | { 28 | return [ 29 | WidgetStatsOverview::class, 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Resources/WidgetAreaResource/Pages/ListWidgetAreas.php: -------------------------------------------------------------------------------- 1 | url(route('filament.admin.pages.appearance')) 19 | ->icon('heroicon-o-paint-brush') 20 | ->color('success') 21 | ->label(__('filawidget::filawidget.Appearance')), 22 | Actions\CreateAction::make()->icon('heroicon-o-plus'), 23 | ]; 24 | } 25 | 26 | protected function getHeaderWidgets(): array 27 | { 28 | return [ 29 | WidgetAreaStatsOverview::class, 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /resources/svg/move.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) IbrahimBougaoua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Models/WidgetArea.php: -------------------------------------------------------------------------------- 1 | where('status', true); 19 | } 20 | 21 | public function scopeOrdered($query) 22 | { 23 | return $query->orderBy('order'); 24 | } 25 | 26 | public static function updateOrder(array $order) 27 | { 28 | foreach ($order as $index => $id) { 29 | self::where('id', $id)->update(['order' => $index + 1]); 30 | } 31 | } 32 | 33 | public function widgets() 34 | { 35 | return $this->hasMany(Widget::class); 36 | } 37 | 38 | public function scopeWithOrderedWidgets($query) 39 | { 40 | return $query->with(['widgets' => function ($query) { 41 | $query->orderBy('order'); 42 | }]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /resources/svg/areas.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/svg/fields.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Models/Widget.php: -------------------------------------------------------------------------------- 1 | 'array', 15 | ]; 16 | 17 | public function scopeActive($query) 18 | { 19 | return $query->where('status', true); 20 | } 21 | 22 | public function scopeOrdered($query) 23 | { 24 | return $query->orderBy('order'); 25 | } 26 | 27 | public static function updateOrder(array $order) 28 | { 29 | foreach ($order as $index => $id) { 30 | self::where('id', $id)->update(['order' => $index + 1]); 31 | } 32 | } 33 | 34 | public function area() 35 | { 36 | return $this->belongsTo(WidgetArea::class, 'widget_area_id'); 37 | } 38 | 39 | public function type() 40 | { 41 | return $this->belongsTo(WidgetType::class, 'widget_type_id'); 42 | } 43 | 44 | public function values() 45 | { 46 | return $this->hasMany(WidgetField::class); 47 | } 48 | 49 | public function fields() 50 | { 51 | //return $this->belongsToMany(Field::class, 'widget_fields', 'widget_id', 'widget_field_id'); 52 | return $this->hasMany(Field::class, 'widget_fields', 'widget_id', 'widget_field_id'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Resources/WidgetAreaResource/Widgets/WidgetAreaStatsOverview.php: -------------------------------------------------------------------------------- 1 | count(); 17 | 18 | $inactiveWidgetAreas = WidgetArea::where('status', false)->count(); 19 | 20 | $totalWidgets = Widget::count(); 21 | 22 | return [ 23 | Stat::make(__('filawidget::filawidget.Total Widget Areas'), $totalWidgetAreas) 24 | ->description(__('filawidget::filawidget.Wide Total number of widget areas created Area')) 25 | ->color('primary'), 26 | Stat::make(__('filawidget::filawidget.Active Widget Areas'), $activeWidgetAreas) 27 | ->description(__('filawidget::filawidget.Number of active widget areas')) 28 | ->color('success'), 29 | Stat::make(__('filawidget::filawidget.Inactive Widget Areas'), $inactiveWidgetAreas) 30 | ->description(__('filawidget::filawidget.Number of inactive widget areas')) 31 | ->color('danger'), 32 | Stat::make(__('filawidget::filawidget.Total Widgets'), $totalWidgets) 33 | ->description(__('filawidget::filawidget.Number of widgets in all areas')) 34 | ->color('warning'), 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Models/Page.php: -------------------------------------------------------------------------------- 1 | where('status', true); 20 | } 21 | 22 | public function scopeFather($query) 23 | { 24 | return $query->whereNull('parent_id'); 25 | } 26 | 27 | public function scopeChild($query) 28 | { 29 | return $query->whereNotNull('parent_id'); 30 | } 31 | 32 | public function scopeOrdered($query) 33 | { 34 | return $query->orderBy('order'); 35 | } 36 | 37 | public function scopeChildOrdered($query) 38 | { 39 | return $query->orderBy('child_order'); 40 | } 41 | 42 | public static function updateOrder(array $order) 43 | { 44 | foreach ($order as $index => $id) { 45 | self::where('id', $id)->update(['order' => $index + 1]); 46 | } 47 | } 48 | 49 | public function isRoot() 50 | { 51 | return is_null($this->parent_id) || $this->parent_id === ''; 52 | } 53 | 54 | public function children() 55 | { 56 | return $this->hasMany(Page::class, 'parent_id'); 57 | } 58 | 59 | public function parent() 60 | { 61 | return $this->belongsTo(Page::class, 'parent_id'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Resources/WidgetResource/Widgets/WidgetStatsOverview.php: -------------------------------------------------------------------------------- 1 | count(); 16 | 17 | $inactiveWidgets = Widget::active()->count(); 18 | 19 | $widgetTypeStats = Widget::select('widget_type_id') 20 | ->selectRaw('count(*) as count') 21 | ->groupBy('widget_type_id') 22 | ->get(); 23 | 24 | return [ 25 | Stat::make(__('filawidget::filawidget.Total Widgets'), $totalWidgets) 26 | ->description(__('filawidget::filawidget.Total number of widgets created')) 27 | ->color('primary'), 28 | Stat::make(__('filawidget::filawidget.Active Widgets'), $activeWidgets) 29 | ->description(__('filawidget::filawidget.Number of active widgets')) 30 | ->color('success'), 31 | Stat::make(__('filawidget::filawidget.Inactive Widgets'), $inactiveWidgets) 32 | ->description(__('filawidget::filawidget.Number of inactive widgets')) 33 | ->color('danger'), 34 | Stat::make(__('filawidget::filawidget.Widgets Per Type'), $widgetTypeStats->pluck('count')->sum()) 35 | ->description(__('filawidget::filawidget.Number of widgets by type')) 36 | ->color('warning'), 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /resources/svg/edit-property.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/components/footer.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 11 |
12 |
13 |
14 |
15 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/Resources/WidgetResource/Pages/EditWidget.php: -------------------------------------------------------------------------------- 1 | record->id; 25 | 26 | // Retrieve form state 27 | $data = $this->form->getState(); 28 | 29 | // Delete existing WidgetFields for this widget to avoid duplicates 30 | WidgetField::where('widget_id', $widgetId)->delete(); 31 | 32 | // Check if there are any repeater values 33 | if (isset($data['values']) && is_array($data['values'])) { 34 | foreach ($data['values'] as $fieldData) { 35 | // Iterate through each field in the repeater 36 | foreach ($fieldData as $fieldName => $value) { 37 | // Ensure the field exists in the repeater and find its corresponding field ID 38 | if ($fieldName) { 39 | //dd($fieldName); 40 | // Find the correct field ID based on the name or position 41 | $fieldIdIndex = array_search($fieldName, array_keys($fieldData)); 42 | $fieldId = $this->record->fieldsIds[$fieldIdIndex] ?? null; 43 | //dd('widget_id = ' . $widgetId,' widget_field_id = ' . $fieldId . ' value = ' . $value, ); 44 | if ($fieldId) { 45 | // Save each field value 46 | WidgetField::create([ 47 | 'widget_id' => $widgetId, 48 | 'widget_field_id' => $fieldId, // Use the correct field ID 49 | 'value' => $value, // Save the actual value from the repeater 50 | ]); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /resources/svg/drawing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/FilaWidgetPlugin.php: -------------------------------------------------------------------------------- 1 | resources([ 36 | PageResource::class, 37 | WidgetResource::class, 38 | WidgetAreaResource::class, 39 | WidgetFieldResource::class, 40 | WidgetTypeResource::class, 41 | ]) 42 | ->pages([ 43 | Appearance::class, 44 | ]) 45 | ->widgets([ 46 | WidgetStatsOverview::class, 47 | WidgetAreaStatsOverview::class, 48 | ]); 49 | } 50 | 51 | public function boot(Panel $panel): void 52 | { 53 | if (config('filawidget.show_home_link')) { 54 | FilamentView::registerRenderHook( 55 | PanelsRenderHook::USER_MENU_BEFORE, 56 | fn (): View => view('filawidget::components.home'), 57 | ); 58 | } 59 | 60 | if (Route::currentRouteName() === 'filament.admin.pages.appearance') { 61 | FilamentView::registerRenderHook( 62 | PanelsRenderHook::CONTENT_START, 63 | fn (): View => view('filawidget::components.filter'), 64 | ); 65 | } 66 | 67 | if (config('filawidget.show_quick_appearance')) { 68 | FilamentView::registerRenderHook( 69 | PanelsRenderHook::TOPBAR_START, 70 | fn (): View => view('filawidget::components.quick'), 71 | ); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ibrahimbougaoua/filawidget", 3 | "description": "This is my package filawidget", 4 | "keywords": [ 5 | "IbrahimBougaoua", 6 | "laravel", 7 | "filawidget" 8 | ], 9 | "homepage": "https://github.com/anmeo2204/filawidget", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Ibrahim Bougaoua", 14 | "email": "bb.team.bougaoua@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "spatie/laravel-package-tools": "^1.16", 21 | "illuminate/contracts": "^10.0||^11.0||^12.0" 22 | }, 23 | "require-dev": { 24 | "laravel/pint": "^1.14", 25 | "nunomaduro/collision": "^8.1.1||^7.10.0", 26 | "larastan/larastan": "^2.9", 27 | "orchestra/testbench": "^9.0.0||^8.22.0", 28 | "pestphp/pest": "^2.34", 29 | "pestphp/pest-plugin-arch": "^2.7", 30 | "pestphp/pest-plugin-laravel": "^2.3", 31 | "phpstan/extension-installer": "^1.3", 32 | "phpstan/phpstan-deprecation-rules": "^1.1", 33 | "phpstan/phpstan-phpunit": "^1.3", 34 | "spatie/laravel-ray": "^1.35" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "IbrahimBougaoua\\Filawidget\\": "src/", 39 | "IbrahimBougaoua\\Filawidget\\Database\\Factories\\": "database/factories/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "IbrahimBougaoua\\Filawidget\\Tests\\": "tests/", 45 | "Workbench\\App\\": "workbench/app/" 46 | } 47 | }, 48 | "scripts": { 49 | "post-autoload-dump": "@composer run prepare", 50 | "clear": "@php vendor/bin/testbench package:purge-filawidget --ansi", 51 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 52 | "build": [ 53 | "@composer run prepare", 54 | "@php vendor/bin/testbench workbench:build --ansi" 55 | ], 56 | "start": [ 57 | "Composer\\Config::disableProcessTimeout", 58 | "@composer run build", 59 | "@php vendor/bin/testbench serve" 60 | ], 61 | "analyse": "vendor/bin/phpstan analyse", 62 | "test": "vendor/bin/pest", 63 | "test-coverage": "vendor/bin/pest --coverage", 64 | "format": "vendor/bin/pint" 65 | }, 66 | "config": { 67 | "sort-packages": true, 68 | "allow-plugins": { 69 | "pestphp/pest-plugin": true, 70 | "phpstan/extension-installer": true 71 | } 72 | }, 73 | "extra": { 74 | "laravel": { 75 | "providers": [ 76 | "IbrahimBougaoua\\Filawidget\\FilawidgetServiceProvider" 77 | ], 78 | "aliases": { 79 | "Filawidget": "IbrahimBougaoua\\Filawidget\\Facades\\Filawidget" 80 | } 81 | } 82 | }, 83 | "minimum-stability": "dev", 84 | "prefer-stable": true 85 | } 86 | -------------------------------------------------------------------------------- /resources/views/components/quick.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
-------------------------------------------------------------------------------- /database/migrations/create_filawidget_table.php.stub: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('title'); 14 | $table->string('slug')->unique(); 15 | $table->text('content')->nullable(); 16 | $table->integer('order')->default(0); 17 | $table->integer('child_order')->default(0); 18 | $table->boolean('status')->default(false); 19 | $table->foreignId('parent_id')->nullable()->constrained('pages')->onDelete('cascade'); 20 | $table->timestamps(); 21 | }); 22 | 23 | Schema::create('widget_areas', function (Blueprint $table) { 24 | $table->id(); 25 | $table->string('name'); 26 | $table->string('identifier')->unique(); 27 | $table->text('description')->nullable(); 28 | $table->integer('order')->default(0); 29 | $table->boolean('status')->default(false); 30 | $table->timestamps(); 31 | }); 32 | 33 | Schema::create('widget_types', function (Blueprint $table) { 34 | $table->id(); 35 | $table->string('name'); 36 | $table->string('slug'); 37 | $table->json('config')->nullable(); 38 | $table->text('fieldsIds')->nullable(); 39 | $table->timestamps(); 40 | }); 41 | 42 | Schema::create('fields', function (Blueprint $table) { 43 | $table->id(); 44 | $table->string('name'); 45 | $table->string('type'); 46 | $table->json('options')->nullable(); 47 | $table->timestamps(); 48 | }); 49 | 50 | Schema::create('widgets', function (Blueprint $table) { 51 | $table->id(); 52 | $table->string('name'); 53 | $table->string('slug'); 54 | $table->text('description')->nullable(); 55 | $table->integer('order')->default(0); 56 | $table->boolean('status')->default(false); 57 | $table->foreignId('widget_area_id')->constrained('widget_areas')->onDelete('cascade'); 58 | $table->foreignId('widget_type_id')->constrained('widget_types')->onDelete('cascade'); 59 | $table->timestamps(); 60 | }); 61 | 62 | Schema::create('widget_fields', function (Blueprint $table) { 63 | $table->longText('value')->nullable(); 64 | $table->foreignId('widget_id')->constrained('widgets')->onDelete('cascade'); 65 | $table->foreignId('widget_field_id')->constrained('fields')->onDelete('cascade'); 66 | $table->primary(['widget_id', 'widget_field_id']); 67 | }); 68 | } 69 | 70 | public function down() 71 | { 72 | Schema::dropIfExists('pages'); 73 | Schema::dropIfExists('widgets'); 74 | Schema::dropIfExists('fields'); 75 | Schema::dropIfExists('widget_fields'); 76 | Schema::dropIfExists('widget_areas'); 77 | Schema::dropIfExists('widget_types'); 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /resources/lang/ar/filawidget.php: -------------------------------------------------------------------------------- 1 | 'إدارة المظهر', 5 | 'Dashboard' => 'لوحة التحكم', 6 | 'Appearance' => 'المظهر', 7 | 'Appearances' => 'المظاهر', 8 | 'Pages' => 'الصفحات', 9 | 'Page' => 'الصفحة', 10 | 'Widgets' => 'الأدوات', 11 | 'Widget' => 'الأدات', 12 | 'Preview' => 'معاينة', 13 | 'Widget Areas' => 'مناطق الأدوات', 14 | 'Fields' => 'الحقول', 15 | 'Field' => 'الحقل', 16 | 'Widget Types' => 'أنواع الأدوات', 17 | 'Managing pages and subpages' => 'إدارة الصفحات والصفحات الفرعية', 18 | 'Drag-and-drop interface to manage the order of managing page and subpages, allowing for a fully customizable page layout without the need for coding.' => 'واجهة السحب والإفلات لإدارة ترتيب الصفحات والصفحات الفرعية، مما يسمح بتخطيط صفحة قابل للتخصيص بالكامل دون الحاجة إلى الترميز.', 19 | 'Preview areas and widgets' => 'معاينة المناطق والأدوات', 20 | 'Drag-and-drop interface to manage the order of widgets within each area, allowing for a fully customizable page layout without the need for coding.' => 'واجهة السحب والإفلات لإدارة ترتيب الأدوات داخل كل منطقة، مما يسمح بتخطيط صفحة قابل للتخصيص بالكامل دون الحاجة إلى الترميز.', 21 | 'No Widget Found' => 'لم يتم العثور على عنصر واجهة مستخدم', 22 | 'Areas with their widgets.' => 'المناطق مع الأدوات الخاصة بها.', 23 | 'Change the order of areas and widgets just by using Drag-and-drop.' => 'قم بتغيير ترتيب المناطق والأدوات باستخدام السحب والإفلات.', 24 | 'No Widgets' => 'لا توجد أدوات', 25 | 'Create a widget to get started.' => 'أنشئ عنصر واجهة مستخدم للبدء.', 26 | 'Create New Widget' => 'إنشاء عنصر واجهة مستخدم جديد', 27 | 'Pages with subpages' => 'الصفحات مع الصفحات الفرعية', 28 | 'Change the order of pages and subpages just by using Drag-and-drop.' => 'قم بتغيير ترتيب الصفحات والصفحات الفرعية باستخدام السحب والإفلات.', 29 | 'No Pages' => 'لا توجد صفحات', 30 | 'Create a Pages to get started.' => 'أنشئ صفحات للبدء.', 31 | 'Create New Pages' => 'إنشاء صفحات جديدة', 32 | 'Invalid input.' => 'إدخال غير صالح.', 33 | 'Area order successfully updated.' => 'تم تحديث ترتيب المناطق بنجاح.', 34 | 'Widgets order successfully updated.' => 'تم تحديث ترتيب الأدوات بنجاح.', 35 | 'Pages order successfully updated.' => 'تم تحديث ترتيب الصفحات بنجاح.', 36 | 'Title' => 'العنوان', 37 | 'Root' => 'الجذر', 38 | 'Content' => 'المحتوى', 39 | 'Status' => 'الحالة', 40 | 'Created at' => 'تاريخ الإنشاء', 41 | 'Area Name' => 'اسم المنطقة', 42 | 'Identifier' => 'المعرف', 43 | 'This identifier is used to reference the widget area in your code.' => 'يُستخدم هذا المعرف للإشارة إلى منطقة الأدوات في الكود الخاص بك.', 44 | 'Field Name' => 'اسم الحقل', 45 | 'Field Type' => 'نوع الحقل', 46 | 'Options' => 'خيارات', 47 | 'Provide additional options in JSON format (e.g., {"default": "value", "validation": "required|max:255"})' => 'قدّم خيارات إضافية بتنسيق JSON (على سبيل المثال، {"الافتراضي": "القيمة", "التحقق": "مطلوب|الحد الأقصى: 255"})', 48 | 'Used' => 'مستخدم', 49 | 'Area' => 'المنطقة', 50 | 'Name' => 'الاسم', 51 | 'Widget Type' => 'نوع الأداة', 52 | 'Description' => 'الوصف', 53 | 'Configurations' => 'التكوينات', 54 | 'Display Fields' => 'عرض الحقول', 55 | 'Widget Area' => 'منطقة الأدوات', 56 | 'Managing dynamic content and layouts' => 'إدارة المحتوى الديناميكي والتخطيطات', 57 | 'Total Widgets' => 'إجمالي الأدوات', 58 | 'Total number of widgets created' => 'العدد الإجمالي للأدوات التي تم إنشاؤها', 59 | 'Active Widgets' => 'الأدوات النشطة', 60 | 'Number of active widgets' => 'عدد الأدوات النشطة', 61 | 'Inactive Widgets' => 'الأدوات غير النشطة', 62 | 'Number of inactive widgets' => 'عدد الأدوات غير النشطة', 63 | 'Widgets Per Type' => 'الأدوات حسب النوع', 64 | 'Number of widgets by type' => 'عدد الأدوات حسب النوع', 65 | 'Total Widget Areas' => 'إجمالي مناطق الأدوات', 66 | 'Total number of widget areas created' => 'إجمالي عدد مناطق الأدوات التي تم إنشاؤها', 67 | 'Active Widget Areas' => 'مناطق الأدوات النشطة', 68 | 'Number of active widget areas' => 'عدد مناطق الأدوات النشطة', 69 | 'Inactive Widget Areas' => 'مناطق الأدوات غير النشطة', 70 | 'Number of inactive widget areas' => 'عدد مناطق الأدوات غير النشطة', 71 | 'Number of widgets in all areas' => 'عدد الأدوات في جميع المناطق', 72 | 'Wide Total number of widget areas created Area' => 'إجمالي عدد مناطق الأدوات التي تم إنشاؤها', 73 | ]; 74 | -------------------------------------------------------------------------------- /resources/lang/en/filawidget.php: -------------------------------------------------------------------------------- 1 | 'Appearance Management', 5 | 'Dashboard' => 'Dashboard', 6 | 'Appearance' => 'Appearance', 7 | 'Appearances' => 'Appearances', 8 | 'Pages' => 'Pages', 9 | 'Page' => 'Page', 10 | 'Widgets' => 'Widgets', 11 | 'Widget' => 'Widget', 12 | 'Preview' => 'Preview', 13 | 'Widget Areas' => 'Widget Areas', 14 | 'Fields' => 'Fields', 15 | 'Field' => 'Field', 16 | 'Widget Types' => 'Widget Types', 17 | 'Managing pages and subpages' => 'Managing pages and subpages', 18 | 'Drag-and-drop interface to manage the order of managing page and subpages, allowing for a fully customizable page layout without the need for coding.' => 'Drag-and-drop interface to manage the order of managing page and subpages, allowing for a fully customizable page layout without the need for coding.', 19 | 'Preview areas and widgets' => 'Preview areas and widgets', 20 | 'Drag-and-drop interface to manage the order of widgets within each area, allowing for a fully customizable page layout without the need for coding.' => 'Drag-and-drop interface to manage the order of widgets within each area, allowing for a fully customizable page layout without the need for coding.', 21 | 'No Widget Found' => 'No Widget Found', 22 | 'Areas with their widgets.' => 'Areas with their widgets.', 23 | 'Change the order of areas and widgets just by using Drag-and-drop.' => 'Change the order of areas and widgets just by using Drag-and-drop.', 24 | 'No Widgets' => 'No Widgets', 25 | 'Create a widget to get started.' => 'Create a widget to get started.', 26 | 'Create New Widget' => 'Create New Widget', 27 | 'Pages with subpages' => 'Pages with subpages', 28 | 'Change the order of pages and subpages just by using Drag-and-drop.' => 'Change the order of pages and subpages just by using Drag-and-drop.', 29 | 'No Pages' => 'No Pages', 30 | 'Create a Pages to get started.' => 'Create a Pages to get started.', 31 | 'Create New Pages' => 'Create New Pages', 32 | 'Invalid input.' => 'Invalid input.', 33 | 'Area order successfully updated.' => 'Area order successfully updated.', 34 | 'Widgets order successfully updated.' => 'Widgets order successfully updated.', 35 | 'Pages order successfully updated.' => 'Pages order successfully updated.', 36 | 'Title' => 'Title', 37 | 'Root' => 'Root', 38 | 'Content' => 'Content', 39 | 'Status' => 'Status', 40 | 'Created at' => 'Created at', 41 | 'Area Name' => 'Area Name', 42 | 'Identifier' => 'Identifier', 43 | 'This identifier is used to reference the widget area in your code.' => 'This identifier is used to reference the widget area in your code.', 44 | 'Field Name' => 'Field Name', 45 | 'Field Type' => 'Field Type', 46 | 'Options' => 'Options', 47 | 'Provide additional options in JSON format (e.g., {"default": "value", "validation": "required|max:255"})' => 'Provide additional options in JSON format (e.g., {"default": "value", "validation": "required|max:255"})', 48 | 'Used' => 'Used', 49 | 'Area' => 'Area', 50 | 'Name' => 'Name', 51 | 'Widget Type' => 'Widget Type', 52 | 'Description' => 'Description', 53 | 'Configurations' => 'Configurations', 54 | 'Display Fields' => 'Display Fields', 55 | 'Widget Area' => 'Widget Area', 56 | 'Managing dynamic content and layouts' => 'Managing dynamic content and layouts', 57 | 'Total Widgets' => 'Total Widgets', 58 | 'Total number of widgets created' => 'Total number of widgets created', 59 | 'Active Widgets' => 'Active Widgets', 60 | 'Number of active widgets' => 'Number of active widgets', 61 | 'Inactive Widgets' => 'Inactive Widgets', 62 | 'Number of inactive widgets' => 'Number of inactive widgets', 63 | 'Widgets Per Type' => 'Widgets Per Type', 64 | 'Number of widgets by type' => 'Number of widgets by type', 65 | 'Total Widget Areas' => 'Total Widget Areas', 66 | 'Total number of widget areas created' => 'Total number of widget areas created', 67 | 'Active Widget Areas' => 'Active Widget Areas', 68 | 'Number of active widget areas' => 'Number of active widget areas', 69 | 'Inactive Widget Areas' => 'Inactive Widget Areas', 70 | 'Number of inactive widget areas' => 'Number of inactive widget areas', 71 | 'Number of widgets in all areas' => 'Number of widgets in all areas', 72 | 'Wide Total number of widget areas created Area' => 'Wide Total number of widget areas created Area', 73 | ]; 74 | -------------------------------------------------------------------------------- /resources/lang/fr/filawidget.php: -------------------------------------------------------------------------------- 1 | 'Gestion de l\'apparence', 5 | 'Dashboard' => 'Tableau de bord', 6 | 'Appearance' => 'Apparence', 7 | 'Appearances' => 'Apparences', 8 | 'Pages' => 'Pages', 9 | 'Page' => 'Page', 10 | 'Widgets' => 'Widgets', 11 | 'Widget' => 'Widget', 12 | 'Preview' => 'Aperçu', 13 | 'Widget Areas' => 'Zones de widgets', 14 | 'Fields' => 'Champs', 15 | 'Field' => 'Champ', 16 | 'Widget Types' => 'Types de widgets', 17 | 'Managing pages and subpages' => 'Gestion des pages et des sous-pages', 18 | 'Drag-and-drop interface to manage the order of managing page and subpages, allowing for a fully customizable page layout without the need for coding.' => 'Interface glisser-déposer pour gérer l\'ordre de gestion des pages et des sous-pages, permettant une mise en page entièrement personnalisable sans avoir besoin de codage.', 19 | 'Preview areas and widgets' => 'Zones d\'aperçu et widgets', 20 | 'Drag-and-drop interface to manage the order of widgets within each area, allowing for a fully customizable page layout without the need for coding.' => 'Interface glisser-déposer pour gérer l\'ordre des widgets dans chaque zone, permettant une mise en page entièrement personnalisable sans avoir besoin de codage.', 21 | 'No Widget Found' => 'Aucun widget trouvé', 22 | 'Areas with their widgets.' => 'Zones avec leurs widgets.', 23 | 'Change the order of areas and widgets just by using Drag-and-drop.' => 'Modifiez l\'ordre des zones et des widgets simplement en utilisant le glisser-déposer.', 24 | 'No Widgets' => 'Aucun widget', 25 | 'Create a widget to get started.' => 'Créez un widget pour commencer.', 26 | 'Create New Widget' => 'Créer un nouveau widget', 27 | 'Pages with subpages' => 'Pages avec sous-pages', 28 | 'Change the order of pages and subpages just by using Drag-and-drop.' => 'Modifiez l\'ordre des pages et des sous-pages simplement en utilisant le glisser-déposer.', 29 | 'No Pages' => 'Aucune page', 30 | 'Create a Pages to get started.' => 'Créer une page pour commencer.', 31 | 'Create New Pages' => 'Créer de nouvelles pages', 32 | 'Invalid input.' => 'Entrée non valide.', 33 | 'Area order successfully updated.' => 'Ordre des zones mis à jour avec succès.', 34 | 'Widgets order successfully updated.' => 'Ordre des widgets mis à jour avec succès.', 35 | 'Pages order successfully updated.' => 'L\'ordre des pages a été mis à jour avec succès.', 36 | 'Title' => 'Titre', 37 | 'Root' => 'Racine', 38 | 'Content' => 'Contenu', 39 | 'Status' => 'État', 40 | 'Created at' => 'Créé à', 41 | 'Area Name' => 'Nom de la zone', 42 | 'Identifier' => 'Identifiant', 43 | 'This identifier is used to reference the widget area in your code.' => 'Cet identifiant est utilisé pour référencer la zone de widget dans votre code.', 44 | 'Field Name' => 'Nom du champ', 45 | 'Field Type' => 'Type de champ', 46 | 'Options' => 'Options', 47 | 'Provide additional options in JSON format (e.g., {"default": "value", "validation": "required|max:255"})' => 'Fournir des options supplémentaires au format JSON (par exemple, {"default": "value", "validation": "required|max:255"})', 48 | 'Used' => 'Utilisé', 49 | 'Area' => 'Zone', 50 | 'Name' => 'Nom', 51 | 'Widget Type' => 'Type de widget', 52 | 'Description' => 'Description', 53 | 'Configurations' => 'Configurations', 54 | 'Display Fields' => 'Champs d\'affichage', 55 | 'Widget Area' => 'Zone de widget', 56 | 'Managing dynamic content and layouts' => 'Gestion du contenu dynamique et des mises en page', 57 | 'Total Widgets' => 'Total des widgets', 58 | 'Total number of widgets created' => 'Nombre total de widgets créés', 59 | 'Active Widgets' => 'Widgets actifs', 60 | 'Number of active widgets' => 'Nombre de widgets actifs', 61 | 'Inactive Widgets' => 'Widgets inactifs', 62 | 'Number of inactive widgets' => 'Nombre de widgets inactifs', 63 | 'Widgets Per Type' => 'Widgets par type', 64 | 'Number of widgets by type' => 'Nombre de widgets par type', 65 | 'Total Widget Areas' => 'Total des zones de widgets', 66 | 'Total number of widget areas created' => 'Nombre total de zones de widgets créées', 67 | 'Active Widget Areas' => 'Zones de widgets actives', 68 | 'Number of active widget areas' => 'Nombre de zones de widgets actives', 69 | 'Inactive Widget Areas' => 'Zones de widgets inactives', 70 | 'Number of inactive widget areas' => 'Nombre de zones de widgets inactives', 71 | 'Number of widgets in all areas' => 'Nombre de widgets dans toutes les zones', 72 | 'Wide Total number of widget areas created Area' => 'Nombre total de zones de widgets créées', 73 | ]; 74 | -------------------------------------------------------------------------------- /src/Resources/WidgetAreaResource.php: -------------------------------------------------------------------------------- 1 | schema([ 59 | Section::make() 60 | ->schema([ 61 | TextInput::make('name') 62 | ->label(__('filawidget::filawidget.Area Name')) 63 | ->required(), 64 | TextInput::make('identifier') 65 | ->label(__('filawidget::filawidget.Identifier')) 66 | ->unique(ignoreRecord: true) 67 | ->helperText(__('filawidget::filawidget.This identifier is used to reference the widget area in your code.')) 68 | ->required(), 69 | RichEditor::make('description') 70 | ->label(__('filawidget::filawidget.Description')) 71 | ->columnSpanFull(), 72 | Toggle::make('status') 73 | ->label(__('filawidget::filawidget.Status')) 74 | ->columnSpanFull(), 75 | ]) 76 | ->columns(2), 77 | ]); 78 | } 79 | 80 | public static function table(Table $table): Table 81 | { 82 | return $table 83 | ->columns([ 84 | TextColumn::make('name') 85 | ->badge() 86 | ->color('success') 87 | ->label(__('filawidget::filawidget.Area Name')), 88 | TextColumn::make('identifier') 89 | ->badge() 90 | ->color('primary') 91 | ->label(__('filawidget::filawidget.Identifier')), 92 | ToggleColumn::make('status') 93 | ->label(__('filawidget::filawidget.Status')), 94 | TextColumn::make('created_at') 95 | ->dateTime('d, M Y h:s A') 96 | ->badge() 97 | ->color('success') 98 | ->label(__('filawidget::filawidget.Created at')), 99 | ]) 100 | ->filters([ 101 | // 102 | ]) 103 | ->actions([ 104 | Tables\Actions\ViewAction::make(), 105 | Tables\Actions\EditAction::make(), 106 | Tables\Actions\DeleteAction::make(), 107 | ]) 108 | ->bulkActions([ 109 | Tables\Actions\BulkActionGroup::make([ 110 | Tables\Actions\DeleteBulkAction::make(), 111 | ]), 112 | ]) 113 | ->modifyQueryUsing(fn (Builder $query) => $query->ordered()); 114 | } 115 | 116 | public static function getRelations(): array 117 | { 118 | return [ 119 | // 120 | ]; 121 | } 122 | 123 | public static function getPages(): array 124 | { 125 | return [ 126 | 'index' => Pages\ListWidgetAreas::route('/'), 127 | 'create' => Pages\CreateWidgetArea::route('/create'), 128 | 'edit' => Pages\EditWidgetArea::route('/{record}/edit'), 129 | ]; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Pages/Appearance.php: -------------------------------------------------------------------------------- 1 | filter = $request->query('filter', 'widgets'); 74 | 75 | $this->pages = PageService::getAllPages(); 76 | $this->widgets = WidgetService::getAllWidgets(); 77 | $this->widgetAreas = AreaService::getAllAreasWithOrderedWidgets(); 78 | $this->nbrWidgetAreas = $this->widgetAreas ? count($this->widgetAreas) : 0; 79 | $this->nbrPages = PageService::counts(); 80 | } 81 | 82 | public function updateOrder() 83 | { 84 | if (! is_array($this->widgetsOrder) || ! is_array($this->widgetAreasOrder)) { 85 | return response()->json(['status' => 'error', 'message' => __('filawidget::filawidget.Invalid input.')], 400); 86 | } 87 | 88 | foreach ($this->widgetAreasOrder as $index => $widgetAreaId) { 89 | WidgetArea::where('id', $widgetAreaId)->update(['order' => $index + 1]); 90 | } 91 | 92 | foreach ($this->widgetsOrder as $index => $widgetId) { 93 | Widget::where('id', $widgetId)->update(['order' => $index + 1]); 94 | } 95 | 96 | $this->widgetAreas = WidgetArea::ordered()->withOrderedWidgets()->get(); 97 | 98 | if ($this->widgetAreasOrder != []) { 99 | session()->flash('areaStatus', __('filawidget::filawidget.Area order successfully updated.')); 100 | } 101 | 102 | if ($this->widgetsOrder != []) { 103 | session()->flash('widgetStatus', __('filawidget::filawidget.Widgets order successfully updated.')); 104 | } 105 | } 106 | 107 | public function updatePageOrder() 108 | { 109 | if (! is_array($this->pagesOrder) || ! is_array($this->subPagesOrder)) { 110 | return response()->json(['status' => 'error', 'message' => __('filawidget::filawidget.Invalid input.')], 400); 111 | } 112 | 113 | foreach ($this->pagesOrder as $index => $pageId) { 114 | ModelsPage::father()->where('id', $pageId)->update(['order' => $index + 1]); 115 | } 116 | 117 | foreach ($this->subPagesOrder as $index => $pageId) { 118 | ModelsPage::child()->where('id', $pageId)->update(['child_order' => $index + 1]); 119 | } 120 | 121 | $this->pages = PageService::getAllPages(); 122 | 123 | session()->flash('pageStatus', __('filawidget::filawidget.Pages order successfully updated.')); 124 | } 125 | 126 | public function hideAlert() 127 | { 128 | session()->flash('pageStatus', null); 129 | session()->flash('areaStatus', null); 130 | session()->flash('widgetStatus', null); 131 | } 132 | 133 | public function handleOrderUpdate() 134 | { 135 | if ($this->filter === 'widgets') { 136 | $this->updateOrder(); 137 | } else { 138 | $this->updatePageOrder(); 139 | } 140 | } 141 | 142 | public function getHeader(): ?View 143 | { 144 | return view('filawidget::components.header', [ 145 | 'filter' => $this->filter, 146 | ]); 147 | } 148 | 149 | public function getFooter(): ?View 150 | { 151 | return $this->filter !== 'preview' ? view('filawidget::components.footer') : null; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Resources/PageResource.php: -------------------------------------------------------------------------------- 1 | schema([ 60 | Section::make() 61 | ->schema([ 62 | TextInput::make('title') 63 | ->label(__('filawidget::filawidget.Title')) 64 | ->required() 65 | ->maxLength(255), 66 | Select::make('parent_id') 67 | ->label(__('filawidget::filawidget.Root')) 68 | ->options( 69 | Page::pluck('title', 'id') 70 | ->toArray() 71 | ) 72 | ->default( 73 | request()->has('page_id') ? request()->query('page_id') : null 74 | ) 75 | ->searchable(), 76 | RichEditor::make('content') 77 | ->label(__('filawidget::filawidget.Content')) 78 | ->columnSpanFull(), 79 | Toggle::make('status') 80 | ->label(__('filawidget::filawidget.Status')) 81 | ->inline(false), 82 | ]) 83 | ->columns(2), 84 | ]); 85 | } 86 | 87 | public static function table(Table $table): Table 88 | { 89 | return $table 90 | ->columns([ 91 | Tables\Columns\TextColumn::make('title') 92 | ->label(__('filawidget::filawidget.Title')) 93 | ->badge() 94 | ->color('success') 95 | ->searchable(), 96 | Tables\Columns\TextColumn::make('parent.title') 97 | ->label(__('filawidget::filawidget.Root')) 98 | ->badge() 99 | ->color('warning') 100 | ->sortable() 101 | ->default('-'), 102 | ToggleColumn::make('status') 103 | ->label(__('filawidget::filawidget.Status')), 104 | TextColumn::make('created_at') 105 | ->dateTime('d, M Y h:s A') 106 | ->badge() 107 | ->color('success') 108 | ->label(__('filawidget::filawidget.Created at')), 109 | ]) 110 | ->filters([ 111 | // 112 | ]) 113 | ->actions([ 114 | Tables\Actions\EditAction::make(), 115 | Tables\Actions\ViewAction::make(), 116 | Tables\Actions\DeleteAction::make(), 117 | ]) 118 | ->bulkActions([ 119 | Tables\Actions\BulkActionGroup::make([ 120 | Tables\Actions\DeleteBulkAction::make(), 121 | ]), 122 | ]) 123 | ->modifyQueryUsing(fn (Builder $query) => $query->latest()); 124 | } 125 | 126 | public static function getRelations(): array 127 | { 128 | return [ 129 | // 130 | ]; 131 | } 132 | 133 | public static function getPages(): array 134 | { 135 | return [ 136 | 'index' => Pages\ListPages::route('/'), 137 | 'create' => Pages\CreatePage::route('/create'), 138 | 'edit' => Pages\EditPage::route('/{record}/edit'), 139 | ]; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Resources/WidgetFieldResource.php: -------------------------------------------------------------------------------- 1 | schema([ 55 | Section::make() 56 | ->schema([ 57 | Forms\Components\TextInput::make('name') 58 | ->label(__('filawidget::filawidget.Field Name')) 59 | ->required(), 60 | 61 | Forms\Components\Select::make('type') 62 | ->label(__('filawidget::filawidget.Field Type')) 63 | ->options([ 64 | 'text' => 'Text', 65 | 'textarea' => 'Textarea', 66 | 'number' => 'Number', 67 | 'select' => 'Select', 68 | 'checkbox' => 'Checkbox', 69 | 'radio' => 'Radio', 70 | 'toggle' => 'Toggle', 71 | 'color' => 'Color Picker', 72 | 'date' => 'Date Picker', 73 | 'datetime' => 'Date Time Picker', 74 | 'time' => 'Time Picker', 75 | 'file' => 'File Upload', 76 | 'image' => 'Image Upload', 77 | 'richeditor' => 'Rich Editor', 78 | 'markdown' => 'Markdown Editor', 79 | 'tags' => 'Tags Input', 80 | 'password' => 'Password', 81 | ]) 82 | ->required(), 83 | 84 | Forms\Components\Textarea::make('options') 85 | ->label(__('filawidget::filawidget.Options')) 86 | ->helperText(__('filawidget::filawidget.Provide additional options in JSON format (e.g., {"default": "value", "validation": "required|max:255"})')) 87 | ->columnSpanFull(), 88 | ]) 89 | ->columns(2), 90 | ]); 91 | } 92 | 93 | public static function table(Table $table): Table 94 | { 95 | return $table 96 | ->columns([ 97 | TextColumn::make('name') 98 | ->badge() 99 | ->color('success') 100 | ->label(__('filawidget::filawidget.Field Name')), 101 | TextColumn::make('type') 102 | ->badge() 103 | ->color('warning') 104 | ->label(__('filawidget::filawidget.Field Type')), 105 | TextColumn::make('widgets_count') 106 | ->counts('widgets') 107 | ->badge() 108 | ->color('success') 109 | ->label(__('filawidget::filawidget.Used')), 110 | TextColumn::make('created_at') 111 | ->dateTime('d, M Y h:s A') 112 | ->badge() 113 | ->color('success') 114 | ->label(__('filawidget::filawidget.Created at')), 115 | ]) 116 | ->filters([ 117 | // 118 | ]) 119 | ->actions([ 120 | Tables\Actions\ViewAction::make(), 121 | Tables\Actions\EditAction::make(), 122 | Tables\Actions\DeleteAction::make(), 123 | ]) 124 | ->bulkActions([ 125 | Tables\Actions\BulkActionGroup::make([ 126 | Tables\Actions\DeleteBulkAction::make(), 127 | ]), 128 | ]); 129 | } 130 | 131 | public static function getRelations(): array 132 | { 133 | return [ 134 | // 135 | ]; 136 | } 137 | 138 | public static function getPages(): array 139 | { 140 | return [ 141 | 'index' => Pages\ListWidgetFields::route('/'), 142 | 'create' => Pages\CreateWidgetField::route('/create'), 143 | 'edit' => Pages\EditWidgetField::route('/{record}/edit'), 144 | ]; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Resources/WidgetTypeResource.php: -------------------------------------------------------------------------------- 1 | schema([ 59 | Section::make() 60 | ->schema([ 61 | TextInput::make('name') 62 | ->label(__('filawidget::filawidget.Name')) 63 | ->required(), 64 | Select::make('fieldsIds') 65 | ->label(__('filawidget::filawidget.Fields')) 66 | ->options( 67 | WidgetsField::pluck('name', 'id')->toArray() 68 | ) 69 | ->multiple() 70 | ->reactive() 71 | ->required(), 72 | Repeater::make('fields') 73 | ->schema(function (callable $get) { 74 | 75 | $fields = WidgetsField::whereIn('id', $get('fieldsIds'))->get(['name', 'type', 'options', 'id'])->toArray(); 76 | 77 | return collect($fields)->map(function ($field) { 78 | $component = match ($field['type']) { 79 | 'text' => Forms\Components\TextInput::make("config.{$field['name']}"), 80 | 'textarea' => Forms\Components\Textarea::make("config.{$field['name']}"), 81 | 'number' => Forms\Components\TextInput::make("config.{$field['name']}")->numeric(), 82 | 'select' => Forms\Components\Select::make("config.{$field['name']}") 83 | ->options($field['options'] ?? []), 84 | 'checkbox' => Forms\Components\Checkbox::make("config.{$field['name']}"), 85 | 'radio' => Forms\Components\Radio::make("config.{$field['name']}") 86 | ->options($field['options'] ?? []), 87 | 'toggle' => Forms\Components\Toggle::make("config.{$field['name']}"), 88 | 'color' => Forms\Components\ColorPicker::make("config.{$field['name']}"), 89 | 'date' => Forms\Components\DatePicker::make("config.{$field['name']}"), 90 | 'datetime' => Forms\Components\DateTimePicker::make("config.{$field['name']}"), 91 | 'time' => Forms\Components\TimePicker::make("config.{$field['name']}"), 92 | 'file' => Forms\Components\FileUpload::make("config.{$field['name']}"), 93 | 'image' => Forms\Components\FileUpload::make("config.{$field['name']}")->image(), 94 | 'richeditor' => Forms\Components\RichEditor::make("config.{$field['name']}"), 95 | 'markdown' => Forms\Components\MarkdownEditor::make("config.{$field['name']}"), 96 | 'tags' => Forms\Components\TagsInput::make("config.{$field['name']}"), 97 | 'password' => Forms\Components\TextInput::make("config.{$field['name']}")->password(), 98 | default => Forms\Components\TextInput::make("config.{$field['name']}"), 99 | }; 100 | 101 | if (isset($field['default'])) { 102 | $component->default($field['default']); 103 | } 104 | 105 | if (isset($field['validation'])) { 106 | $component->rules($field['validation']); 107 | } 108 | 109 | return $component->label(ucfirst(str_replace('_', ' ', $field['name']))); 110 | })->toArray(); 111 | }) 112 | ->label(__('filawidget::filawidget.Configurations')) 113 | ->maxItems(1) 114 | ->minItems(1) 115 | ->reorderable(false) 116 | ->deletable(false) 117 | ->required() 118 | ->reactive() 119 | ->defaultItems(1) 120 | ->addActionLabel(__('filawidget::filawidget.Display Fields')) 121 | ->columnSpanFull(), 122 | ]) 123 | ->columns(2), 124 | ]); 125 | } 126 | 127 | public static function table(Table $table): Table 128 | { 129 | return $table 130 | ->columns([ 131 | TextColumn::make('name') 132 | ->badge() 133 | ->color('success') 134 | ->label(__('filawidget::filawidget.Name')), 135 | TextColumn::make('widgets_count') 136 | ->badge() 137 | ->color('warning') 138 | ->counts('widgets') 139 | ->label(__('filawidget::filawidget.Widgets')), 140 | TextColumn::make('created_at') 141 | ->dateTime('d, M Y h:s A') 142 | ->badge() 143 | ->color('success') 144 | ->label(__('filawidget::filawidget.Created at')), 145 | ]) 146 | ->filters([ 147 | // 148 | ]) 149 | ->actions([ 150 | Tables\Actions\ViewAction::make(), 151 | Tables\Actions\EditAction::make(), 152 | Tables\Actions\DeleteAction::make(), 153 | ]) 154 | ->bulkActions([ 155 | Tables\Actions\BulkActionGroup::make([ 156 | Tables\Actions\DeleteBulkAction::make(), 157 | ]), 158 | ]); 159 | } 160 | 161 | public static function getRelations(): array 162 | { 163 | return [ 164 | // 165 | ]; 166 | } 167 | 168 | public static function getPages(): array 169 | { 170 | return [ 171 | 'index' => Pages\ListWidgets::route('/'), 172 | 'create' => Pages\CreateWidget::route('/create'), 173 | 'edit' => Pages\EditWidget::route('/{record}/edit'), 174 | ]; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /resources/views/components/filter.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 36 |
37 | 99 |
100 | 101 | -------------------------------------------------------------------------------- /resources/views/components/header.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @if($filter == "widgets") 4 |
5 |

{{ __('filawidget::filawidget.Managing dynamic content and layouts') }}

6 |

{{ __('filawidget::filawidget.Drag-and-drop interface to manage the order of widgets within each area, allowing for a fully customizable page layout without the need for coding.') }}

7 | 41 |
42 | @elseif($filter == "pages") 43 |
44 |

{{ __('filawidget::filawidget.Managing pages and subpages') }}

45 |

{{ __('filawidget::filawidget.Drag-and-drop interface to manage the order of managing page and subpages, allowing for a fully customizable page layout without the need for coding.') }}

46 |
47 | @else 48 |
49 |

{{ __('filawidget::filawidget.Preview areas and widgets') }}

50 |

{{ __('filawidget::filawidget.Drag-and-drop interface to manage the order of widgets within each area, allowing for a fully customizable page layout without the need for coding.') }}

51 |
52 | @endif 53 | 54 | 55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 |
87 |
88 |
-------------------------------------------------------------------------------- /src/Resources/WidgetResource.php: -------------------------------------------------------------------------------- 1 | schema([ 79 | Section::make() 80 | ->schema([ 81 | TextInput::make('name') 82 | ->label(__('filawidget::filawidget.Name')) 83 | ->required() 84 | ->columnSpanFull(), 85 | Select::make('widget_area_id') 86 | ->label(__('filawidget::filawidget.Area')) 87 | ->options( 88 | WidgetArea::pluck('name', 'id')->toArray() 89 | ) 90 | ->required() 91 | ->searchable() 92 | ->default( 93 | request()->has('area_id') ? request()->query('area_id') : null 94 | ), 95 | Select::make('widget_type_id') 96 | ->label(__('filawidget::filawidget.Widget Type')) 97 | ->searchable() 98 | ->options( 99 | WidgetType::pluck('name', 'id')->toArray() 100 | ) 101 | ->afterStateUpdated(function (callable $set, $state) { 102 | $widgetType = WidgetType::find($state); 103 | if ($widgetType) { 104 | $set('fieldsIds', $widgetType->fieldsIds); 105 | } 106 | }) 107 | ->reactive() 108 | ->required(), 109 | RichEditor::make('description') 110 | ->label(__('filawidget::filawidget.Description')) 111 | ->columnSpanFull(), 112 | Toggle::make('status') 113 | ->label(__('filawidget::filawidget.Status')), 114 | Hidden::make('fieldsIds') 115 | ->reactive(), 116 | Repeater::make('values') 117 | ->label(__('filawidget::filawidget.Appearance')) 118 | ->schema(function (callable $get) { 119 | 120 | $fieldsIds = $get('fieldsIds') ?? []; 121 | 122 | $widgetId = $get('id') ?? null; 123 | 124 | $fields = []; 125 | if (is_array($fieldsIds) && count($fieldsIds) > 0) { 126 | $fields = WidgetsField::whereIn('id', $fieldsIds) 127 | ->get(['fields.name', 'fields.type', 'fields.options', 'fields.id']) 128 | ->toArray(); 129 | } 130 | 131 | $values = []; 132 | if (! is_null($widgetId) && is_array($fieldsIds) && count($fieldsIds) > 0) { 133 | $values = WidgetField::where('widget_id', $widgetId) 134 | ->whereIn('widget_field_id', $fieldsIds) 135 | ->get(['widget_field_id', 'value']) 136 | ->pluck('value', 'widget_field_id') 137 | ->toArray(); 138 | } 139 | 140 | return collect($fields)->map(function ($field) use ($values) { 141 | 142 | $options = json_decode($field['options'], true); 143 | 144 | $defaultValue = $options['default'] ?? ''; 145 | 146 | $component = match ($field['type']) { 147 | 'text' => TextInput::make($field['name']), 148 | 'textarea' => Textarea::make($field['name']), 149 | 'number' => TextInput::make($field['name'])->numeric(), 150 | 'select' => Select::make($field['name']) 151 | ->options($field['options'] ?? []), 152 | 'checkbox' => Checkbox::make($field['name']), 153 | 'radio' => Radio::make($field['name']) 154 | ->options($field['options'] ?? []), 155 | 'toggle' => Toggle::make($field['name']), 156 | 'color' => ColorPicker::make($field['name']), 157 | 'date' => DatePicker::make($field['name']), 158 | 'datetime' => DateTimePicker::make($field['name']), 159 | 'time' => TimePicker::make($field['name']), 160 | 'file' => FileUpload::make($field['name']), 161 | 'image' => FileUpload::make($field['name'])->image(), 162 | 'richeditor' => RichEditor::make($field['name']), 163 | 'markdown' => MarkdownEditor::make($field['name']), 164 | 'tags' => TagsInput::make($field['name']), 165 | 'password' => TextInput::make($field['name'])->password(), 166 | default => TextInput::make($field['name']), 167 | }; 168 | 169 | $component->default($values[$field['id']] ?? $defaultValue); 170 | 171 | if (isset($field['validation'])) { 172 | $component->rules($field['validation']); 173 | } 174 | 175 | return $component->label(ucfirst(str_replace('_', ' ', $field['name']))); 176 | })->toArray(); 177 | 178 | }) 179 | ->label(__('filawidget::filawidget.Configurations')) 180 | ->reorderable(false) 181 | ->deletable(false) 182 | ->reactive() 183 | ->defaultItems(1) 184 | ->addActionLabel(__('filawidget::filawidget.Display Fields')) 185 | ->columnSpanFull(), 186 | ]) 187 | ->columns(2), 188 | ]); 189 | } 190 | 191 | public static function table(Table $table): Table 192 | { 193 | return $table 194 | ->columns([ 195 | TextColumn::make('name') 196 | ->badge() 197 | ->color('success') 198 | ->label(__('filawidget::filawidget.Widget')), 199 | TextColumn::make('type.name') 200 | ->badge() 201 | ->color('primary') 202 | ->label(__('filawidget::filawidget.Widget Type')), 203 | SelectColumn::make('widget_area_id') 204 | ->options(WidgetArea::pluck('name', 'id')->toArray()) 205 | ->label(__('filawidget::filawidget.Widget Area')), 206 | ToggleColumn::make('status') 207 | ->label(__('filawidget::filawidget.Status')), 208 | TextColumn::make('created_at') 209 | ->dateTime('d, M Y h:s A') 210 | ->badge() 211 | ->color('success') 212 | ->label(__('filawidget::filawidget.Created at')), 213 | ]) 214 | ->filters([ 215 | SelectFilter::make('widget_area_id') 216 | ->label(__('filawidget::filawidget.Widget Area')) 217 | ->options(WidgetArea::pluck('name', 'id')->toArray()), 218 | Filter::make('created_at') 219 | ->label(__('filawidget::filawidget.Created at')) 220 | ->form([ 221 | DatePicker::make('created_from'), 222 | DatePicker::make('created_until'), 223 | ]) 224 | ->query(function (Builder $query, array $data): Builder { 225 | return $query 226 | ->when( 227 | $data['created_from'], 228 | fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date), 229 | ) 230 | ->when( 231 | $data['created_until'], 232 | fn (Builder $query, $date): Builder => $query->whereDate('created_at', '<=', $date), 233 | ); 234 | }), 235 | ]) 236 | ->actions([ 237 | Tables\Actions\ViewAction::make(), 238 | Tables\Actions\EditAction::make(), 239 | Tables\Actions\DeleteAction::make(), 240 | ]) 241 | ->bulkActions([ 242 | Tables\Actions\BulkActionGroup::make([ 243 | Tables\Actions\DeleteBulkAction::make(), 244 | ]), 245 | ]) 246 | ->modifyQueryUsing(fn (Builder $query) => $query->ordered()); 247 | } 248 | 249 | public static function getRelations(): array 250 | { 251 | return [ 252 | // 253 | ]; 254 | } 255 | 256 | public static function getPages(): array 257 | { 258 | return [ 259 | 'index' => Pages\ListWidgets::route('/'), 260 | 'create' => Pages\CreateWidget::route('/create'), 261 | 'edit' => Pages\EditWidget::route('/{record}/edit'), 262 | ]; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Customizable solution for managing dynamic content in Laravel projects using FilamentPHP. 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/ibrahimbougaoua/filawidget.svg?style=flat-square)](https://packagist.org/packages/ibrahimbougaoua/filawidget) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/ibrahimbougaoua/filawidget/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/ibrahimbougaoua/filawidget/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/ibrahimbougaoua/filawidget/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/ibrahimbougaoua/filawidget/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/ibrahimbougaoua/filawidget.svg?style=flat-square)](https://packagist.org/packages/ibrahimbougaoua/filawidget) 7 | 8 | **Filawidget** is a dynamic content and widget management package for **FilamentPHP**, providing an easy-to-use drag-and-drop interface to manage widgets, widget areas, and hierarchical pages. The package is designed to enhance the customization of page layouts and widgets in Laravel projects. 9 | 10 | ## Support us 11 | 12 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/ibrahimbougaoua) 13 | 14 | ## Key Features 15 | 16 | ### Widget Management 17 | - **Create and Customize Widgets**: Users can create widgets with custom fields, types, and configurations. Each widget can have a set of configurable fields and is tied to a specific widget area. 18 | - **Drag-and-Drop Interface**: An easy-to-use drag-and-drop interface allows users to organize widgets within areas dynamically, rearranging widget order effortlessly. 19 | - **Active/Inactive Widgets**: Manage the status of widgets, setting them as active or inactive based on visibility requirements. 20 | 21 | ### Widget Areas 22 | - **Multiple Widget Areas**: Define different widget areas (e.g., sidebars, footers) to which widgets can be assigned. 23 | - **Drag-and-Drop Layout Customization**: Users can rearrange the order of both widget areas and the widgets within them using drag-and-drop functionality, ensuring customizable page layouts. 24 | - **Active/Inactive Widget Areas**: Control the visibility of widget areas, marking them as active or inactive depending on layout preferences. 25 | 26 | ### Page and Subpage Management 27 | - **Hierarchical Pages**: Manage pages and subpages in a hierarchical structure with parent-child relationships. Pages can be organized into different levels of hierarchy, facilitating complex website structures. 28 | - **Page Status**: Set pages as active or inactive based on publishing needs, allowing for controlled visibility across the website. 29 | - **Drag-and-Drop Page Reordering**: Reorder pages and subpages easily through a drag-and-drop interface, ensuring flexibility in page hierarchy and content organization. 30 | 31 | ### Dynamic Field Configuration 32 | - **Custom Fields for Widgets**: Add custom fields to widgets, such as text, images, or other input types, through a dynamic and configurable system. 33 | - **JSON-Based Field Options**: Provide flexible options for fields, enabling validation rules, default values, and other configurations in JSON format. 34 | 35 | ### Widget Types 36 | - **Custom Widget Types**: Create different types of widgets with specific functionalities, allowing for a wide range of content management options. 37 | - **Field Association with Widget Types**: Assign different sets of fields to widget types, ensuring each widget type has the necessary input fields for customization. 38 | 39 | ### Order Management 40 | - **Customizable Widget and Page Orders**: Users can update the order of widgets, pages, and widget areas. Each item can be repositioned dynamically, offering complete control over the structure. 41 | - **Automated Order Updates**: Use the built-in functionality to update the order of widgets and pages across the system automatically. 42 | 43 | ### Note : 44 | Screenshots from the client project. 45 | 46 |
47 | Youtube Video 48 | 49 |
50 |

Widgets

51 |
52 | 53 | [](https://www.youtube.com/watch?v=KyedlYpU5i0) 54 | 55 |
56 |

Pages

57 |
58 | 59 | [](https://www.youtube.com/watch?v=KyedlYpU5i0) 60 | 61 |
62 |

Preview

63 |
64 | 65 | [](https://www.youtube.com/watch?v=KyedlYpU5i0) 66 | 67 |
68 |

Frontend

69 |
70 | 71 | [](https://www.youtube.com/watch?v=KyedlYpU5i0) 72 | 73 | ## Installation 74 | 75 | You can install the package via composer: 76 | 77 | ```bash 78 | composer require ibrahimbougaoua/filawidget 79 | ``` 80 | 81 | You can publish and run the migrations with: 82 | 83 | ```bash 84 | php artisan vendor:publish --tag="filawidget-migrations" 85 | php artisan migrate 86 | ``` 87 | 88 | You can publish the config file with: 89 | 90 | ```bash 91 | php artisan vendor:publish --tag="filawidget-config" 92 | ``` 93 | 94 | This is the contents of the published config file: 95 | 96 | ```php 97 | return [ 98 | 'should_register_navigation_appearance' => true, 99 | 'should_register_navigation_pages' => true, 100 | 'should_register_navigation_widgets' => true, 101 | 'should_register_navigation_widget_areas' => true, 102 | 'should_register_navigation_fields' => true, 103 | 'should_register_navigation_widget_types' => true, 104 | 'show_home_link' => true, 105 | 'show_quick_appearance' => true, 106 | ]; 107 | ``` 108 | 109 | Optionally, you can publish the views using 110 | 111 | ```bash 112 | php artisan vendor:publish --tag="filawidget-views" 113 | ``` 114 | 115 | Available fields of filament that can use it for create dynamic widget. 116 | 117 | ```bash 118 | ---------------------------------------- 119 | | Field Type | Description | 120 | |-------------------|------------------| 121 | | Text | Text Field | 122 | | Textarea | Textarea Field | 123 | | Number | Number Input | 124 | | Select | Select Dropdown | 125 | | Checkbox | Checkbox | 126 | | Radio | Radio Button | 127 | | Toggle | Toggle Switch | 128 | | Color Picker | Color Picker | 129 | | Date Picker | Date Picker | 130 | | Date Time Picker | Date Time Picker | 131 | | Time Picker | Time Picker | 132 | | File Upload | File Upload | 133 | | Image Upload | Image Upload | 134 | | Rich Editor | Rich Text Editor | 135 | | Markdown Editor | Markdown Editor | 136 | | Tags Input | Tags Input | 137 | | Password | Password Input | 138 | ---------------------------------------- 139 | ``` 140 | 141 | ## Usage 142 | 143 | ```php 144 | // AdminPanelProvider 145 | use IbrahimBougaoua\Filawidget\FilaWidgetPlugin; 146 | 147 | public function panel(Panel $panel): Panel 148 | { 149 | return $panel 150 | ->plugins([ 151 | FilaWidgetPlugin::make(), 152 | ]); 153 | } 154 | ``` 155 | 156 | ```php 157 | // Areas 158 | use IbrahimBougaoua\Filawidget\Services\AreaService; 159 | 160 | $areas = AreaService::getAllAreas(); 161 | $areasWithOrderedWidgets = AreaService::getAllAreasWithOrderedWidgets(); 162 | $area = AreaService::getWidgetByIdentifier("Sidebar"); 163 | ``` 164 | 165 | ```php 166 | // Widgets 167 | use IbrahimBougaoua\Filawidget\Services\WidgetService; 168 | 169 | $widgets = WidgetService::getAllWidgets(); 170 | $widget = WidgetService::getWidgetBySlug("latest-posts"); 171 | ``` 172 | 173 | ```php 174 | // Pages 175 | use IbrahimBougaoua\Filawidget\Services\PageService; 176 | 177 | $pages = PageService::getAllPages(); 178 | $page = PageService::getPageBySlug("about-us"); 179 | $counts = PageService::counts(); 180 | ``` 181 | 182 | ```php 183 | use IbrahimBougaoua\Filawidget\Services\AreaService; 184 | use IbrahimBougaoua\Filawidget\Services\PageService; 185 | 186 | // Route 187 | Route::get('/', function(){ 188 | $pages = PageService::getAllPages(); 189 | $areas = AreaService::getAllAreas(); 190 | 191 | return view('welcome',[ 192 | 'pages' => $pages, 193 | 'areas' => $areas, 194 | ]); 195 | }); 196 | 197 | // Welcome Blade 198 | 199 | 200 | 201 | 202 | 203 | 204 | Filament Widgets 205 | 217 | 218 | 219 |
220 | 221 |
222 | 262 |
263 | 264 | @foreach ($areas as $area) 265 |
266 | @forelse ($area->widgets as $widget) 267 |
268 |
269 |
270 | {{ $widget->name }} 271 |
272 |
273 |

274 | {{ $widget->description }} 275 |

276 |
277 |
278 |
279 | @empty 280 |
281 |
282 |
283 |

No Widget Found

284 |

˟

285 |
286 |
287 |
288 | @endforelse 289 |
290 | @endforeach 291 |
292 | 293 | 294 | 295 | 296 | 297 | 298 | ``` 299 | 300 | ## Testing 301 | 302 | ```bash 303 | composer test 304 | ``` 305 | 306 | ## Changelog 307 | 308 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 309 | 310 | ## Contributing 311 | 312 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 313 | 314 | ## Security Vulnerabilities 315 | 316 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 317 | 318 | ## Credits 319 | 320 | - [Ibrahim Bougaoua](https://github.com/ibrahim bougaoua) 321 | - [All Contributors](../../contributors) 322 | 323 | ## License 324 | 325 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 326 | -------------------------------------------------------------------------------- /resources/views/pages/appearance.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @if ($filter === 'preview') 3 | 4 |
5 |
6 | 7 | @foreach ($widgetAreas as $area) 8 |
9 |
10 |
11 |

12 | {{ $area->name }} ({{ count($area->widgets) ?? 0 }}) 13 |

14 |
15 |
16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 | @forelse ($area->widgets as $widget) 24 |
25 | 26 |
27 |
28 |

29 | {{ $widget->name }} 30 |

31 |
32 |
33 |
34 | @empty 35 |
36 | 37 |
38 |
39 |

{{ __('filawidget::filawidget.No Widget Found') }}

40 |
41 |
42 |
43 | @endforelse 44 |
45 |
46 | @endforeach 47 |
48 |
49 | 50 | @elseif ($filter === 'widgets') 51 |
52 | 53 | @if (session('areaStatus')) 54 |
55 |
56 |
57 | {{ session('areaStatus') }} 58 |
59 |
60 | 61 | 62 | 63 |
64 |
65 |
66 | @endif 67 | 68 | @if (session('widgetStatus')) 69 |
70 |
71 |
72 | {{ session('widgetStatus') }} 73 |
74 |
75 | 76 | 77 | 78 |
79 |
80 |
81 | @endif 82 | 83 |
84 |
85 |

{{ __('filawidget::filawidget.Areas with their widgets.') }}

86 | {{ __('filawidget::filawidget.Change the order of areas and widgets just by using Drag-and-drop.') }} 87 |
88 |
89 | 96 | 101 |
102 |
103 | 104 | {{-- Container for sortable items --}} 105 |
106 | @foreach ($widgetAreas as $key => $widgetArea) 107 |
108 |
109 |
110 | 117 | 124 |

{{ $widgetArea->name }} ({{ $widgetArea->widgets ? $widgetArea->widgets->count() : 0 }})

125 |
126 |
127 | 128 | 129 | 130 | 131 |
132 |
133 |
134 | @forelse ($widgetArea->widgets as $widget) 135 |
136 |
137 |
138 | 145 |

{{ $widget->name }}

146 |
147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 |
157 |
158 |
159 | @empty 160 |
161 |
162 |
163 | 166 |
167 | 168 |

169 | {{ __('filawidget::filawidget.No Widgets') }} 170 |

171 | 172 |

173 | {{ __('filawidget::filawidget.Create a widget to get started.') }} 174 |

175 | 176 | 194 |
195 |
196 | @endforelse 197 |
198 |
199 | @endforeach 200 |
201 |
202 | 203 | 204 | {{-- Include SortableJS from CDN --}} 205 | 206 | 207 | 236 | @else 237 | 238 |
239 | 240 | @if (session('pageStatus')) 241 |
242 |
243 |
244 | {{ session('pageStatus') }} 245 |
246 |
247 | 248 | 249 | 250 |
251 |
252 |
253 | @endif 254 | 255 |
256 |
257 |

{{ __('filawidget::filawidget.Pages with subpages') }}

258 | {{ __('filawidget::filawidget.Change the order of pages and subpages just by using Drag-and-drop.') }} 259 |
260 |
261 | 268 | 275 | 280 |
281 |
282 | 283 | {{-- Container for sortable items --}} 284 |
285 | @foreach ($pages as $key => $page) 286 |
287 |
288 |
289 | 296 | 303 |

{{ $page->title }} ({{ $page->children ? $page->children->count() : 0 }})

304 |
305 |
306 | 307 | 308 | 309 | 310 |
311 |
312 |
313 | @forelse ($page->children->sortBy('child_order') as $sub_page) 314 |
315 |
316 |
317 | 324 |

{{ $sub_page->title }}

325 |
326 |
327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 |
336 |
337 |
338 | @empty 339 |
340 |
341 |
342 | 345 |
346 | 347 |

348 | {{ __('filawidget::filawidget.No Pages') }} 349 |

350 | 351 |

352 | {{ __('filawidget::filawidget.Create a Pages to get started.') }} 353 |

354 | 355 | 373 |
374 |
375 | @endforelse 376 |
377 |
378 | @endforeach 379 |
380 |
381 | 382 | 383 | {{-- Include SortableJS from CDN --}} 384 | 385 | 386 | 415 | @endif 416 |
417 | --------------------------------------------------------------------------------