From ecf17c00e69838872bc0d14b584b5049a84eced6 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 23 May 2024 13:57:15 +0100 Subject: [PATCH 1/7] Allow asset container order to be specified --- src/Assets/AssetContainer.php | 8 +++ src/CP/Navigation/CoreNav.php | 2 +- .../CP/Assets/AssetContainersController.php | 2 +- .../Assets/RedirectsToFirstAssetContainer.php | 2 +- src/Stache/Stores/AssetContainersStore.php | 3 +- tests/Assets/AssetContainerTest.php | 12 ++++ .../ListAssetContainersTest.php | 59 +++++++++++++++++++ 7 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php index 020d6f7833..e43df8dd88 100644 --- a/src/Assets/AssetContainer.php +++ b/src/Assets/AssetContainer.php @@ -49,6 +49,7 @@ class AssetContainer implements Arrayable, ArrayAccess, AssetContainerContract, protected $withEvents = true; protected $sortField; protected $sortDirection; + protected $order = 1; public function id($id = null) { @@ -655,6 +656,13 @@ public function searchIndex($index = null) ->args(func_get_args()); } + public function order($order = null) + { + return $this + ->fluentlyGetOrSet('order') + ->args(func_get_args()); + } + public static function __callStatic($method, $parameters) { return Facades\AssetContainer::{$method}(...$parameters); diff --git a/src/CP/Navigation/CoreNav.php b/src/CP/Navigation/CoreNav.php index 45c6616a22..b1aa58f810 100644 --- a/src/CP/Navigation/CoreNav.php +++ b/src/CP/Navigation/CoreNav.php @@ -122,7 +122,7 @@ protected function makeContentSection() ->icon('/assets') ->can('index', AssetContainer::class) ->children(function () { - return AssetContainerAPI::all()->sortBy->title()->map(function ($assetContainer) { + return AssetContainerAPI::all()->sortBy(fn ($container) => [$container->order(), $container->title()])->map(function ($assetContainer) { return Nav::item($assetContainer->title()) ->url($assetContainer->showUrl()) ->can('view', $assetContainer); diff --git a/src/Http/Controllers/CP/Assets/AssetContainersController.php b/src/Http/Controllers/CP/Assets/AssetContainersController.php index 57f0a50fa5..aaeead196b 100644 --- a/src/Http/Controllers/CP/Assets/AssetContainersController.php +++ b/src/Http/Controllers/CP/Assets/AssetContainersController.php @@ -19,7 +19,7 @@ public function show($container) public function index(Request $request) { - $containers = AssetContainer::all()->sortBy->title()->filter(function ($container) { + $containers = AssetContainer::all()->sortBy(fn ($container) => [$container->order(), $container->title()])->filter(function ($container) { return User::current()->can('view', $container); })->map(function ($container) { return [ diff --git a/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php b/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php index 6430e84bb9..d7a9571333 100644 --- a/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php +++ b/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php @@ -9,7 +9,7 @@ trait RedirectsToFirstAssetContainer { public function redirectToFirstContainer() { - $containers = AssetContainer::all()->sortBy->title()->filter(function ($container) { + $containers = AssetContainer::all()->sortBy(fn ($container) => [$container->order(), $container->title()])->filter(function ($container) { return User::current()->can('view', $container); }); diff --git a/src/Stache/Stores/AssetContainersStore.php b/src/Stache/Stores/AssetContainersStore.php index 536ea7b000..450a505d44 100644 --- a/src/Stache/Stores/AssetContainersStore.php +++ b/src/Stache/Stores/AssetContainersStore.php @@ -30,6 +30,7 @@ public function makeItemFromFile($path, $contents) ->warmPresets(Arr::get($data, 'warm_presets')) ->searchIndex(Arr::get($data, 'search_index')) ->sortField(Arr::get($data, 'sort_by')) - ->sortDirection(Arr::get($data, 'sort_dir')); + ->sortDirection(Arr::get($data, 'sort_dir')) + ->order(Arr::get($data, 'order', 1)); } } diff --git a/tests/Assets/AssetContainerTest.php b/tests/Assets/AssetContainerTest.php index 40c2b53996..76b6d4f65c 100644 --- a/tests/Assets/AssetContainerTest.php +++ b/tests/Assets/AssetContainerTest.php @@ -1064,4 +1064,16 @@ private function containerWithDisk($fixture = 'container') return $container; } + + /** @test */ + public function it_gets_and_sets_the_order() + { + $container = new AssetContainer; + $this->assertSame(1, $container->order()); + + $return = $container->order(3); + + $this->assertEquals($container, $return); + $this->assertSame(3, $container->order()); + } } diff --git a/tests/Feature/AssetContainers/ListAssetContainersTest.php b/tests/Feature/AssetContainers/ListAssetContainersTest.php index bae094e710..d57b6f1a61 100644 --- a/tests/Feature/AssetContainers/ListAssetContainersTest.php +++ b/tests/Feature/AssetContainers/ListAssetContainersTest.php @@ -46,6 +46,65 @@ public function it_loads_a_view_when_requested_normally() ->assertViewHas('containers', $this->containerArray()); } + /** @test */ + public function it_lists_container_by_order_specified() + { + $this->setTestRoles(['test' => ['access cp', 'view one assets', 'view two assets', 'view three assets']]); + $user = User::make()->assignRole('test')->save(); + AssetContainer::make('one')->order(3)->save(); + AssetContainer::make('two')->order(2)->save(); + AssetContainer::make('three')->order(1)->save(); + + $this + ->actingAs($user) + ->getJson(cp_route('asset-containers.index')) + ->assertSuccessful() + ->assertJson([ + [ + 'id' => 'three', + 'title' => 'Three', + 'allow_downloading' => true, + 'allow_moving' => true, + 'allow_renaming' => true, + 'allow_uploads' => true, + 'create_folders' => true, + 'edit_url' => 'http://localhost/cp/asset-containers/three/edit', + 'delete_url' => 'http://localhost/cp/asset-containers/three', + 'blueprint_url' => 'http://localhost/cp/asset-containers/three/blueprint', + 'can_edit' => false, + 'can_delete' => false, + ], + [ + 'id' => 'two', + 'title' => 'Two', + 'allow_downloading' => true, + 'allow_moving' => true, + 'allow_renaming' => true, + 'allow_uploads' => true, + 'create_folders' => true, + 'edit_url' => 'http://localhost/cp/asset-containers/two/edit', + 'delete_url' => 'http://localhost/cp/asset-containers/two', + 'blueprint_url' => 'http://localhost/cp/asset-containers/two/blueprint', + 'can_edit' => false, + 'can_delete' => false, + ], + [ + 'id' => 'one', + 'title' => 'One', + 'allow_downloading' => true, + 'allow_moving' => true, + 'allow_renaming' => true, + 'allow_uploads' => true, + 'create_folders' => true, + 'edit_url' => 'http://localhost/cp/asset-containers/one/edit', + 'delete_url' => 'http://localhost/cp/asset-containers/one', + 'blueprint_url' => 'http://localhost/cp/asset-containers/one/blueprint', + 'can_edit' => false, + 'can_delete' => false, + ], + ]); + } + public function containerArray() { return [ From 9d73d4c92c26a5a3eeb73230f92f61aa796f48c3 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 28 May 2024 16:36:40 +0100 Subject: [PATCH 2/7] Handle the fallback in the `order` method --- src/Assets/AssetContainer.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php index e43df8dd88..a012371a1f 100644 --- a/src/Assets/AssetContainer.php +++ b/src/Assets/AssetContainer.php @@ -49,7 +49,7 @@ class AssetContainer implements Arrayable, ArrayAccess, AssetContainerContract, protected $withEvents = true; protected $sortField; protected $sortDirection; - protected $order = 1; + protected $order; public function id($id = null) { @@ -660,6 +660,9 @@ public function order($order = null) { return $this ->fluentlyGetOrSet('order') + ->getter(function ($order) { + return $order ?? 1; + }) ->args(func_get_args()); } From 97ecae7443900b1849b6ebdfa063e3aa7c054d5d Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 28 May 2024 16:37:06 +0100 Subject: [PATCH 3/7] Ensure the order is persisted after saving --- src/Assets/AssetContainer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php index a012371a1f..ff02786541 100644 --- a/src/Assets/AssetContainer.php +++ b/src/Assets/AssetContainer.php @@ -626,6 +626,7 @@ public function fileData() 'create_folders' => $this->createFolders, 'source_preset' => $this->sourcePreset, 'warm_presets' => $this->warmPresets, + 'order' => $this->order, ]; $array = Arr::removeNullValues(array_merge($array, [ From 296013a8d411ca52f801634c50856a6b4b07c673 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 28 May 2024 16:38:38 +0100 Subject: [PATCH 4/7] Don't default to anything here. --- src/Stache/Stores/AssetContainersStore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stache/Stores/AssetContainersStore.php b/src/Stache/Stores/AssetContainersStore.php index 450a505d44..a9002b781d 100644 --- a/src/Stache/Stores/AssetContainersStore.php +++ b/src/Stache/Stores/AssetContainersStore.php @@ -31,6 +31,6 @@ public function makeItemFromFile($path, $contents) ->searchIndex(Arr::get($data, 'search_index')) ->sortField(Arr::get($data, 'sort_by')) ->sortDirection(Arr::get($data, 'sort_dir')) - ->order(Arr::get($data, 'order', 1)); + ->order(Arr::get($data, 'order')); } } From 9fe7e50bf647e1e3573661485a5dedca6cb52dc4 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 29 May 2024 07:09:48 +0100 Subject: [PATCH 5/7] Move sortBy to AssetContainer::all() --- src/CP/Navigation/CoreNav.php | 2 +- src/Http/Controllers/CP/Assets/AssetContainersController.php | 2 +- .../Controllers/CP/Assets/RedirectsToFirstAssetContainer.php | 2 +- src/Stache/Repositories/AssetContainerRepository.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CP/Navigation/CoreNav.php b/src/CP/Navigation/CoreNav.php index b1aa58f810..6c9ff39b26 100644 --- a/src/CP/Navigation/CoreNav.php +++ b/src/CP/Navigation/CoreNav.php @@ -122,7 +122,7 @@ protected function makeContentSection() ->icon('/assets') ->can('index', AssetContainer::class) ->children(function () { - return AssetContainerAPI::all()->sortBy(fn ($container) => [$container->order(), $container->title()])->map(function ($assetContainer) { + return AssetContainerAPI::all()->map(function ($assetContainer) { return Nav::item($assetContainer->title()) ->url($assetContainer->showUrl()) ->can('view', $assetContainer); diff --git a/src/Http/Controllers/CP/Assets/AssetContainersController.php b/src/Http/Controllers/CP/Assets/AssetContainersController.php index aaeead196b..3f60be8a68 100644 --- a/src/Http/Controllers/CP/Assets/AssetContainersController.php +++ b/src/Http/Controllers/CP/Assets/AssetContainersController.php @@ -19,7 +19,7 @@ public function show($container) public function index(Request $request) { - $containers = AssetContainer::all()->sortBy(fn ($container) => [$container->order(), $container->title()])->filter(function ($container) { + $containers = AssetContainer::all()->filter(function ($container) { return User::current()->can('view', $container); })->map(function ($container) { return [ diff --git a/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php b/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php index d7a9571333..df23c788bb 100644 --- a/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php +++ b/src/Http/Controllers/CP/Assets/RedirectsToFirstAssetContainer.php @@ -9,7 +9,7 @@ trait RedirectsToFirstAssetContainer { public function redirectToFirstContainer() { - $containers = AssetContainer::all()->sortBy(fn ($container) => [$container->order(), $container->title()])->filter(function ($container) { + $containers = AssetContainer::all()->filter(function ($container) { return User::current()->can('view', $container); }); diff --git a/src/Stache/Repositories/AssetContainerRepository.php b/src/Stache/Repositories/AssetContainerRepository.php index 9648d70461..ceaf962ce2 100644 --- a/src/Stache/Repositories/AssetContainerRepository.php +++ b/src/Stache/Repositories/AssetContainerRepository.php @@ -21,7 +21,7 @@ public function all(): Collection { $keys = $this->store->paths()->keys(); - return $this->store->getItems($keys); + return $this->store->getItems($keys)->sortBy(fn ($container) => [$container->order(), $container->title()]); } public function find($id): ?AssetContainer From d4b9ed6716409d9afa5f19dfd64e60b873a6b1e3 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 29 May 2024 07:28:09 +0100 Subject: [PATCH 6/7] Add a CP field --- resources/lang/en/messages.php | 1 + .../CP/Assets/AssetContainersController.php | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index 2f0aec2f17..cc822ae0bb 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -15,6 +15,7 @@ 'asset_container_handle_instructions' => 'Used to reference this container on the frontend. It\'s non-trivial to change later.', 'asset_container_intro' => 'Media and document files live in folders on the server or other file storage services. Each of these locations is called a container.', 'asset_container_move_instructions' => 'When enabled will allow users to move files around inside this container.', + 'asset_container_order_instructions' => 'Containers are sorted ascending by this value in the sidebar and asset tabs.', 'asset_container_quick_download_instructions' => 'When enabled will add a quick download button in the Asset Manager.', 'asset_container_rename_instructions' => 'When enabled will allow users to rename the files in this container.', 'asset_container_source_preset_instructions' => 'Uploaded images will be permanently processed using this preset.', diff --git a/src/Http/Controllers/CP/Assets/AssetContainersController.php b/src/Http/Controllers/CP/Assets/AssetContainersController.php index 3f60be8a68..3d6d00bc8f 100644 --- a/src/Http/Controllers/CP/Assets/AssetContainersController.php +++ b/src/Http/Controllers/CP/Assets/AssetContainersController.php @@ -65,6 +65,7 @@ public function edit($container) 'source_preset' => $container->sourcePreset(), 'warm_intelligent' => $intelligent = $container->warmsPresetsIntelligently(), 'warm_presets' => $intelligent ? [] : $container->warmPresets(), + 'order' => $container->order() == 1 ? '' : $container->order(), ]; $fields = ($blueprint = $this->formBlueprint($container)) @@ -99,7 +100,8 @@ public function update(Request $request, $container) ->allowUploads($values['allow_uploads']) ->createFolders($values['create_folders']) ->sourcePreset($values['source_preset']) - ->warmPresets($values['warm_intelligent'] ? null : $values['warm_presets']); + ->warmPresets($values['warm_intelligent'] ? null : $values['warm_presets']) + ->order($values['order']); $container->save(); @@ -147,7 +149,8 @@ public function store(Request $request) ->allowUploads($values['allow_uploads']) ->createFolders($values['create_folders']) ->sourcePreset($values['source_preset']) - ->warmPresets($values['warm_intelligent'] ? null : $values['warm_presets']); + ->warmPresets($values['warm_intelligent'] ? null : $values['warm_presets']) + ->order($values['order']); $container->save(); @@ -302,6 +305,20 @@ protected function formBlueprint($container = null) ], ]); + $fields = array_merge($fields, [ + 'display' => [ + 'display' => __('Display'), + 'fields' => [ + 'order' => [ + 'type' => 'text', + 'display' => __('Order'), + 'instructions' => __('statamic::messages.asset_container_order_instructions'), + 'validate' => 'numeric', + ], + ], + ], + ]); + return Blueprint::makeFromTabs($fields); } From 6510857f0a7480ab61ab030e3ee84d81717875fd Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 29 May 2024 07:42:31 +0100 Subject: [PATCH 7/7] Fix failing tests --- tests/Feature/GraphQL/AssetContainersTest.php | 2 +- tests/Search/Searchables/AssetsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Feature/GraphQL/AssetContainersTest.php b/tests/Feature/GraphQL/AssetContainersTest.php index 15daf85f36..01b9fb778f 100644 --- a/tests/Feature/GraphQL/AssetContainersTest.php +++ b/tests/Feature/GraphQL/AssetContainersTest.php @@ -57,8 +57,8 @@ public function it_queries_asset_containers() ->post('/graphql', ['query' => $query]) ->assertGqlOk() ->assertExactJson(['data' => ['assetContainers' => [ - ['handle' => 'public', 'title' => 'Public'], ['handle' => 'private', 'title' => 'Private'], + ['handle' => 'public', 'title' => 'Public'], ]]]); } diff --git a/tests/Search/Searchables/AssetsTest.php b/tests/Search/Searchables/AssetsTest.php index 41e2a00005..b5a16a4551 100644 --- a/tests/Search/Searchables/AssetsTest.php +++ b/tests/Search/Searchables/AssetsTest.php @@ -42,7 +42,7 @@ public function it_gets_assets($locale, $config, $expected) $provider = $this->makeProvider($locale, $config); // Check if it provides the expected assets. - $this->assertEquals($expected, $provider->provide()->map->filename()->all()); + $this->assertEquals($expected, $provider->provide()->map->filename()->sort()->values()->all()); // Check if the assets are contained by the provider or not. foreach (Asset::all() as $asset) {