From 771c753518fbe31c0e7d51809d9f801231baa9b4 Mon Sep 17 00:00:00 2001 From: pcambra Date: Fri, 9 Apr 2021 06:59:18 -0400 Subject: [PATCH] First stub of the asset location hierarchy editor feature. --- composer.json | 1 + modules/core/asset/asset.info.yml | 1 + modules/core/asset/asset.libraries.yml | 9 + modules/core/asset/asset.links.task.yml | 5 + modules/core/asset/asset.routing.yml | 22 +++ modules/core/asset/js/asset_reorder.js | 173 ++++++++++++++++++ .../src/Controller/AssetReorderController.php | 125 +++++++++++++ modules/core/asset/src/Entity/Asset.php | 1 + 8 files changed, 337 insertions(+) create mode 100644 modules/core/asset/asset.libraries.yml create mode 100644 modules/core/asset/asset.routing.yml create mode 100644 modules/core/asset/js/asset_reorder.js create mode 100644 modules/core/asset/src/Controller/AssetReorderController.php diff --git a/composer.json b/composer.json index c0f40368f..7cd328468 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "drupal/geofield": "^1.22", "drupal/gin": "3.0-alpha34", "drupal/inline_entity_form": "^1.0@RC", + "drupal/inspire_tree": "^1.0", "drupal/jsonapi_extras": "^3.15", "drupal/jsonapi_schema": "^1.0@beta", "drupal/log": "2.x-dev", diff --git a/modules/core/asset/asset.info.yml b/modules/core/asset/asset.info.yml index dffd0d2e3..2113ce70c 100644 --- a/modules/core/asset/asset.info.yml +++ b/modules/core/asset/asset.info.yml @@ -8,4 +8,5 @@ dependencies: - drupal:user - drupal:views - entity:entity + - inspire_tree:inspire_tree - state_machine:state_machine diff --git a/modules/core/asset/asset.libraries.yml b/modules/core/asset/asset.libraries.yml new file mode 100644 index 000000000..29c5d88e0 --- /dev/null +++ b/modules/core/asset/asset.libraries.yml @@ -0,0 +1,9 @@ +reorder: + version: 1.x + js: + js/asset_reorder.js: {} + dependencies: + - core/drupal.message + - core/jquery + - inspire_tree/inspire_tree + - inspire_tree/inspire_tree_dom diff --git a/modules/core/asset/asset.links.task.yml b/modules/core/asset/asset.links.task.yml index f2bf7b0b8..9a7e3fe1b 100644 --- a/modules/core/asset/asset.links.task.yml +++ b/modules/core/asset/asset.links.task.yml @@ -2,3 +2,8 @@ entity.asset.collection: route_name: entity.asset.collection title: 'Assets' base_route: system.admin_content +entity.asset.locations: + route_name: entity.asset.locations + title: 'Locations' + base_route: entity.asset.canonical + weight: 100 diff --git a/modules/core/asset/asset.routing.yml b/modules/core/asset/asset.routing.yml new file mode 100644 index 000000000..49a2c041e --- /dev/null +++ b/modules/core/asset/asset.routing.yml @@ -0,0 +1,22 @@ +asset.locations: + path: '/asset-locations' + defaults: + _title: 'Locations' + _controller: '\Drupal\asset\Controller\AssetReorderController::build' + requirements: + _permission: 'administer assets' + + +entity.asset.locations: + path: '/asset/{asset}/locations' + defaults: + _title: 'Locations' + _controller: '\Drupal\asset\Controller\AssetReorderController::build' + requirements: + _entity_access: 'asset.edit' + _module_dependencies: 'asset' + asset: \d+ + options: + parameters: + asset: + type: entity:asset diff --git a/modules/core/asset/js/asset_reorder.js b/modules/core/asset/js/asset_reorder.js new file mode 100644 index 000000000..beda7b976 --- /dev/null +++ b/modules/core/asset/js/asset_reorder.js @@ -0,0 +1,173 @@ +/** + * @file + * Integration with inspire tree. + */ + +(function ($, Drupal, settings) { + + "use strict"; + + // @TODO drag and drop validate if exist. + // @TODO validate circular references. + + Drupal.behaviors.asset_reorder = { + attach: function (context, settings) { + var tree = new InspireTree({ + data: settings.asset_tree, + }); + new InspireTreeDOM(tree, { + target: '.asset-tree', + dragAndDrop: true + }); + + var changes = {}; + + tree.on('node.drop', function(event, source, target, index) { + var destination = (target === null) ? settings.asset_parent : target.uuid; + if (!changes.hasOwnProperty(source.id)) { + if (source.original_parent !== destination) { + changes[source.id] = { + 'uuid': source.uuid, + 'original_parent': source.original_parent, + 'original_type': source.original_type, + 'destination': destination, + 'type': (target === null) ? settings.asset_parent_type : target.type, + }; + } + } + else { + if (changes[source.id].original_parent !== destination) { + changes[source.id].destination = destination; + } + else { + delete changes[source.id]; + } + } + }); + + $('.asset-tree-reset').on('click', function(event) { + event.preventDefault(); + // Reset the changes so nothing is pushed accidentally. + changes = {}; + // Reset the tree to the original status. + tree.reload(); + }); + + $('.asset-tree-save').on('click', function(event) { + event.preventDefault(); + var entries = Object.entries(changes); + if (entries.length > 0) { + var token = ''; + $.ajax({ + async: false, + url: Drupal.url('session/token'), + success(data) { + if (data) { + token = data; + } + }, + }); + } + + var messages = new Drupal.Message(); + for (var [treeUuid, item] of entries) { + if (item.destination === '' && item.original_parent !== '') { + var deleteItem = { + 'data': [ + { + 'type': 'asset--' + item.original_type, + 'id': item.original_parent, + } + ] + }; + $.ajax({ + type: 'DELETE', + cache: false, + headers: { + 'X-CSRF-Token': token, + }, + url: '/api/asset/' + item.original_type + '/' + item.uuid + '/relationships/parent', + data: JSON.stringify(deleteItem), + contentType: 'application/vnd.api+json', + success: function success(data) { + messages.clear(); + messages.add(Drupal.t('Assets have been saved'), { type: 'status' }); + delete changes.treeUuid + }, + error: function error(xmlhttp) { + var e = new Drupal.AjaxError(xmlhttp); + messages.clear(); + messages.add(e.message, { type: 'error' }); + } + }); + } + else { + var patch = { + 'data': [ + { + 'type': 'asset--' + item.type, + 'id': item.destination, + } + ] + }; + $.ajax({ + type: 'POST', + cache: false, + headers: { + 'X-CSRF-Token': token, + }, + url: '/api/asset/' + item.type + '/' + item.uuid + '/relationships/parent', + data: JSON.stringify(patch), + contentType: 'application/vnd.api+json', + success: function success(data) { + if (item.original_parent !== settings.asset_parent) { + var deleteItem = { + 'data': [ + { + 'type': 'asset--' + item.original_type, + 'id': item.original_parent, + } + ] + }; + + $.ajax({ + type: 'DELETE', + cache: false, + headers: { + 'X-CSRF-Token': token, + }, + url: '/api/asset/' + item.original_type + '/' + item.uuid + '/relationships/parent', + data: JSON.stringify(deleteItem), + contentType: 'application/vnd.api+json', + success: function success(data) { + messages.clear(); + messages.add(Drupal.t('Assets have been saved'), { type: 'status' }); + delete changes.treeUuid + }, + error: function error(xmlhttp) { + var e = new Drupal.AjaxError(xmlhttp); + messages.clear(); + messages.add(e.message, { type: 'error' }); + } + }); + } + else { + messages.clear(); + messages.add(Drupal.t('Assets have been saved'), { type: 'status' }); + } + }, + error: function error(xmlhttp) { + var e = new Drupal.AjaxError(xmlhttp); + messages.clear(); + messages.add(e.message, { type: 'error' }); + } + }); + } + } + }); + + + } + } + +})(jQuery, Drupal, drupalSettings); diff --git a/modules/core/asset/src/Controller/AssetReorderController.php b/modules/core/asset/src/Controller/AssetReorderController.php new file mode 100644 index 000000000..04efeba60 --- /dev/null +++ b/modules/core/asset/src/Controller/AssetReorderController.php @@ -0,0 +1,125 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager') + ); + } + + /** + * Builds the response. + */ + public function build(AssetInterface $asset = NULL) { + $build['content'] = [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'class' => [ + 'asset-tree', + ], + ], + ]; + + $build['save'] = [ + '#type' => 'link', + '#title' => $this->t('Save'), + '#url' => Url::fromRoute(''), + '#attributes' => [ + 'class' => [ + 'asset-tree-save', + 'button', + 'button--primary', + ], + ], + ]; + $build['reset'] = [ + '#type' => 'link', + '#title' => $this->t('Reset'), + '#url' => Url::fromRoute(''), + '#attributes' => [ + 'class' => [ + 'asset-tree-reset', + 'button', + 'button--danger', + ], + ], + ]; + + $build['#attached']['library'][] = 'asset/reorder'; + $build['#attached']['drupalSettings']['asset_tree'] = $this->buildTree($asset); + $build['#attached']['drupalSettings']['asset_parent'] = !empty($asset) ? $asset->uuid() : ''; + $build['#attached']['drupalSettings']['asset_parent_type'] = !empty($asset) ? $asset->bundle() : ''; + return $build; + } + + /** + * @param \Drupal\asset\Entity\AssetInterface|null $asset + * + * @return array + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function buildTree(AssetInterface $asset = NULL) { + $storage = $this->entityTypeManager->getStorage('asset'); + $query = $storage->getQuery(); + if ($asset) { + $query->condition('parent', $asset->id()); + } + else { + $query->condition('parent', NULL, 'IS NULL'); + } + $query->sort('name'); + + $asset_ids = $query->execute(); + /** @var \Drupal\asset\Entity\AssetInterface $children */ + $children = $storage->loadMultiple($asset_ids); + $tree = []; + foreach ($children as $child) { + $element = [ + 'uuid' => $child->uuid(), + 'text' => $child->label(), + 'children' => $this->buildTree($child), + 'type' => $child->bundle(), + ]; + $element['original_parent'] = $asset ? $asset->uuid() : ''; + $element['original_type'] = $asset ? $asset->bundle() : ''; + $tree[] = $element; + } + + return $tree; + } + +} diff --git a/modules/core/asset/src/Entity/Asset.php b/modules/core/asset/src/Entity/Asset.php index 2a05ca942..5d4b87103 100644 --- a/modules/core/asset/src/Entity/Asset.php +++ b/modules/core/asset/src/Entity/Asset.php @@ -74,6 +74,7 @@ use Drupal\user\EntityOwnerTrait; * "delete-form" = "/asset/{asset}/delete", * "delete-multiple-form" = "/asset/delete", * "edit-form" = "/asset/{asset}/edit", + * "locations" = "/asset/{asset}/locations", * "revision" = "/asset/{asset}/revisions/{asset_revision}/view", * "revision-revert-form" = "/asset/{asset}/revisions/{asset_revision}/revert", * "version-history" = "/asset/{asset}/revisions",