├── 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 |
--------------------------------------------------------------------------------
/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 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
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 |
--------------------------------------------------------------------------------
/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 |
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 | [](https://packagist.org/packages/ibrahimbougaoua/filawidget)
4 | [](https://github.com/ibrahimbougaoua/filawidget/actions?query=workflow%3Arun-tests+branch%3Amain)
5 | [](https://github.com/ibrahimbougaoua/filawidget/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
6 | [](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 | [](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 |
223 |
224 |
Navbar
225 |
226 |
227 |
228 |
260 |
261 |
262 |
263 |
264 | @foreach ($areas as $area)
265 |
266 | @forelse ($area->widgets as $widget)
267 |
279 | @empty
280 |
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 |
22 |
23 | @forelse ($area->widgets as $widget)
24 |
34 | @empty
35 |
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 |
64 |
65 |
66 | @endif
67 |
68 | @if (session('widgetStatus'))
69 |
70 |
71 |
72 | {{ session('widgetStatus') }}
73 |
74 |
79 |
80 |
81 | @endif
82 |
83 |
103 |
104 | {{-- Container for sortable items --}}
105 |
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 |
251 |
252 |
253 | @endif
254 |
255 |
282 |
283 | {{-- Container for sortable items --}}
284 |
285 | @foreach ($pages as $key => $page)
286 |
287 |
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 |
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 |
--------------------------------------------------------------------------------