First stub of the asset location hierarchy editor feature.

This commit is contained in:
pcambra 2021-04-09 06:59:18 -04:00 committed by Michael Stenta
parent 51562201ee
commit 771c753518
8 changed files with 337 additions and 0 deletions

View File

@ -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",

View File

@ -8,4 +8,5 @@ dependencies:
- drupal:user
- drupal:views
- entity:entity
- inspire_tree:inspire_tree
- state_machine:state_machine

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -0,0 +1,125 @@
<?php
namespace Drupal\asset\Controller;
use Drupal\asset\Entity\AssetInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Returns responses for asset drag and drop routes.
*/
class AssetReorderController extends ControllerBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The controller constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->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('<none>'),
'#attributes' => [
'class' => [
'asset-tree-save',
'button',
'button--primary',
],
],
];
$build['reset'] = [
'#type' => 'link',
'#title' => $this->t('Reset'),
'#url' => Url::fromRoute('<none>'),
'#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;
}
}

View File

@ -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",