From cd8bb406f45d8a44766d2e466b3b08a940f251dd Mon Sep 17 00:00:00 2001 From: Bob Wezelman Date: Wed, 6 Nov 2024 15:30:50 +0100 Subject: [PATCH] Default responsive menu with caching (#79) --- composer.json | 6 +- config/rapidez/statamic.php | 13 +++ .../views/components/nav-layer.blade.php | 65 ++++++++++++++ resources/views/components/nav.blade.php | 49 +++++++++++ .../views/navigation/header-button.blade.php | 1 + src/Exceptions/NavException.php | 10 +++ src/Facades/RapidezStatamic.php | 17 ++++ src/Listeners/ClearNavTreeCache.php | 19 ++++ src/Listeners/SetCollectionsForNav.php | 19 ++++ src/RapidezStatamic.php | 87 +++++++++++++++++++ src/RapidezStatamicServiceProvider.php | 10 ++- 11 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 resources/views/components/nav-layer.blade.php create mode 100644 resources/views/components/nav.blade.php create mode 100644 resources/views/navigation/header-button.blade.php create mode 100644 src/Exceptions/NavException.php create mode 100644 src/Facades/RapidezStatamic.php create mode 100644 src/Listeners/ClearNavTreeCache.php create mode 100644 src/Listeners/SetCollectionsForNav.php create mode 100644 src/RapidezStatamic.php diff --git a/composer.json b/composer.json index c625727..bc25883 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "require": { "php": "^8.1|^8.2", "rapidez/blade-directives": "^0.6", + "rapidez/core": "^2.13", "justbetter/statamic-glide-directive": "^2.1", "spatie/once": "*", "statamic-rad-pack/runway": "^7.6", @@ -54,7 +55,10 @@ "laravel": { "providers": [ "Rapidez\\Statamic\\RapidezStatamicServiceProvider" - ] + ], + "aliases": { + "RapidezStatamic": "Rapidez\\Statamic\\Facades\\RapidezStatamic" + } } } } diff --git a/config/rapidez/statamic.php b/config/rapidez/statamic.php index 0ff694b..091691f 100644 --- a/config/rapidez/statamic.php +++ b/config/rapidez/statamic.php @@ -78,6 +78,19 @@ ], ], + 'navigation' => [ + // When a menu get's created, these collections will + // automaticly be added to the navigations allowed entries. + // This way, we don't need to update every navigation with + // settings and it will always be right. + 'allowed_collections' => [ + 'pages', + 'brand', + 'category', + 'product', + ] + ], + 'sites' => [ 'default' => [ 'name' => env('APP_NAME', 'Statamic'), diff --git a/resources/views/components/nav-layer.blade.php b/resources/views/components/nav-layer.blade.php new file mode 100644 index 0000000..1a79ff7 --- /dev/null +++ b/resources/views/components/nav-layer.blade.php @@ -0,0 +1,65 @@ +{{-- +This is a mobile navigation slideover component for handling multi-level navigations. It makes use of recursion to handle multiple levels of navigation dynamically. + +## Properties +- `id` Unique identifier for the current navigation layer. +- `children` Navigation children data +- `title` Title displayed in the header. Defaults to "Menu". +- `hasParent` Indicates if the current menu is a child of another menu. Defaults to false. +- `tag` Base HTML tag for the slideover component. Set this to 'form' for the main navigation or 'div' for nested menus. +- `parentUrl` URL used for navigating to the parent item when in a nested menu. Displayed with a "Go to" link. + +## Slots +- `headerButton` This slot allows the customization of the button placed at the left in the header for navigation. +--}} +@props(['id', 'children', 'title' => __('Menu'), 'hasParent' => false, 'tag' => 'form', 'parentUrl' => '']) +@slots(['headerButton']) + + + +
+ @include('rapidez-statamic::navigation.header-button') +
+
+
+ + {{ $slot }} +
+
diff --git a/resources/views/components/nav.blade.php b/resources/views/components/nav.blade.php new file mode 100644 index 0000000..b99face --- /dev/null +++ b/resources/views/components/nav.blade.php @@ -0,0 +1,49 @@ +@props(['nav' => 'nav:main', 'mobileNav' => 'nav:main']) +@php + // This combines multiple navigations into one for desktop and mobile seperately, only neccessary if you have multiple navigations + $navData = collect($nav)->flatMap(fn ($menu) => RapidezStatamic::nav($menu)); + $mobileNavData = collect($mobileNav)->flatMap(fn ($menu) => RapidezStatamic::nav($menu)); +@endphp + + + diff --git a/resources/views/navigation/header-button.blade.php b/resources/views/navigation/header-button.blade.php new file mode 100644 index 0000000..c3401bd --- /dev/null +++ b/resources/views/navigation/header-button.blade.php @@ -0,0 +1 @@ +{{-- This is shown on the first slideover of the mobile menu, this is empty by default but can be overridden --}} diff --git a/src/Exceptions/NavException.php b/src/Exceptions/NavException.php new file mode 100644 index 0000000..90f7340 --- /dev/null +++ b/src/Exceptions/NavException.php @@ -0,0 +1,10 @@ +tree; + + Cache::forget('nav:' . $tree->handle() . '-' . config('rapidez.store')); + Cache::driver('array')->forget('global-link' . '-' . config('rapidez.store')); + } +} diff --git a/src/Listeners/SetCollectionsForNav.php b/src/Listeners/SetCollectionsForNav.php new file mode 100644 index 0000000..0f6d9a6 --- /dev/null +++ b/src/Listeners/SetCollectionsForNav.php @@ -0,0 +1,19 @@ +nav; + + $nav + ->collections(config('rapidez.statamic.navigation.allowed_collections')) + ->save(); + } +} diff --git a/src/RapidezStatamic.php b/src/RapidezStatamic.php new file mode 100644 index 0000000..15f36f0 --- /dev/null +++ b/src/RapidezStatamic.php @@ -0,0 +1,87 @@ +startsWith('nav:'), + NavException::class, + 'You can only use a nav tag to get a navigation tree.' + ); + + return Cache::rememberForever($tag . '-' . config('rapidez.store'), fn() => $this->buildMenu($tag)); + } + + protected function buildMenu(string $key): array + { + $nav = Statamic::tag($key)->fetch(); + + $cacheKey = $key . '-' . config('rapidez.store'); + $this->navCache[$key] = Cache::get($cacheKey, []); + + return $this->buildTree($nav, $key); + } + + protected function buildTree(array $items, string $nav): array + { + $tree = []; + + foreach ($items as $item) { + if ($item['children']) { + $children = $this->buildTree($item['children'], $nav); + if ($children) { + $item['children'] = $children; + } + } + + $item['url'] = $this->determineEntryUrl($item['entry_id']->augmentable(), $nav); + + $tree[] = $item; + } + + return $tree; + } + + public function determineEntryUrl(Entry|Page $entry, string $nav = 'global-link'): string + { + $cacheKey = $nav . '-' . config('rapidez.store'); + + if ( ! isset($this->navCache[$nav][$entry->id()])) { + $linkedRunwayResourceKey = $entry + ->data() + ->keys() + ->firstWhere(fn($field) => collect([ + ...config('rapidez.statamic.navigation.allowed_collections'), + 'linked_', + ])->firstWhere(fn($key) => str($field)->startsWith($key))); + + if (!$linkedRunwayResourceKey || !$entry->{$linkedRunwayResourceKey} || $entry->slug()) { + return $entry->url() ?? ''; + } + + $suffix = match (true) { + str($linkedRunwayResourceKey)->contains('category') => Rapidez::config('catalog/seo/category_url_suffix', ''), + str($linkedRunwayResourceKey)->contains('product') => Rapidez::config('catalog/seo/product_url_suffix', ''), + default => '', + }; + + $this->navCache[$nav][$entry->id()] = '/' . $entry->{$linkedRunwayResourceKey}['url_path'] . $suffix; + + Cache::forever($cacheKey, $this->navCache[$nav]); + } + + return $this->navCache[$nav][$entry->id()]; + } +} diff --git a/src/RapidezStatamicServiceProvider.php b/src/RapidezStatamicServiceProvider.php index 71700fe..b77c578 100644 --- a/src/RapidezStatamicServiceProvider.php +++ b/src/RapidezStatamicServiceProvider.php @@ -2,7 +2,6 @@ namespace Rapidez\Statamic; -use Statamic\Statamic; use Statamic\Sites\Sites; use Statamic\Facades\Site; use Statamic\Facades\Entry; @@ -26,6 +25,10 @@ use Rapidez\Statamic\Http\Controllers\ImportsController; use Rapidez\Statamic\Http\Controllers\StatamicRewriteController; use Rapidez\Statamic\Http\ViewComposers\StatamicGlobalDataComposer; +use Rapidez\Statamic\Listeners\ClearNavTreeCache; +use Rapidez\Statamic\Listeners\SetCollectionsForNav; +use Statamic\Events\NavCreated; +use Statamic\Events\NavTreeSaved; use TorMorten\Eventy\Facades\Eventy; class RapidezStatamicServiceProvider extends ServiceProvider @@ -35,6 +38,8 @@ public function register() $this->app->extend(Sites::class, function () { return new SitesLinkedToMagentoStores(config('statamic.sites')); }); + + $this->app->singleton(RapidezStatamic::class); } public function boot() @@ -97,6 +102,9 @@ public function bootListeners() : self Cache::forget('statamic-globals-' . Site::selected()->handle()); }); + Event::listen(NavCreated::class, SetCollectionsForNav::class); + Event::listen(NavTreeSaved::class, ClearNavTreeCache::class); + Eventy::addFilter('rapidez.statamic.category.entry.data', fn($category) => [ 'title' => $category->name, 'slug' => trim($category->url_key),