diff --git a/config/schema/paragraphs_type.schema.yml b/config/schema/paragraphs_type.schema.yml index 65b3c7e..6263345 100644 --- a/config/schema/paragraphs_type.schema.yml +++ b/config/schema/paragraphs_type.schema.yml @@ -8,6 +8,9 @@ paragraphs.paragraphs_type.*: label: type: label label: 'Label' + icon_uuid: + type: string + label: 'Icon uuid' behavior_plugins: type: sequence label: 'Plugins' diff --git a/css/paragraphs.list-builder.css b/css/paragraphs.list-builder.css new file mode 100644 index 0000000..12fbd07 --- /dev/null +++ b/css/paragraphs.list-builder.css @@ -0,0 +1,7 @@ +.paragraphs-type-icon { + width: 50px; +} + +.paragraphs-type-icon img { + display: block; +} diff --git a/css/paragraphs.list-builder.scss b/css/paragraphs.list-builder.scss new file mode 100644 index 0000000..609b836 --- /dev/null +++ b/css/paragraphs.list-builder.scss @@ -0,0 +1,7 @@ + +.paragraphs-type-icon { + width: 50px; + img { + display: block; + } +} diff --git a/css/paragraphs.widget.scss b/css/paragraphs.widget.scss index 407df3a..504417d 100644 --- a/css/paragraphs.widget.scss +++ b/css/paragraphs.widget.scss @@ -120,4 +120,5 @@ height: 40px; } } + } diff --git a/paragraphs.info.yml b/paragraphs.info.yml index ff7d98d..95b0579 100644 --- a/paragraphs.info.yml +++ b/paragraphs.info.yml @@ -7,6 +7,7 @@ configure: entity.paragraphs_type.collection dependencies: - entity_reference_revisions:entity_reference_revisions + - drupal:file test_dependencies: - diff:diff - replicate:replicate diff --git a/paragraphs.install b/paragraphs.install index de6b33f..3ce6070 100644 --- a/paragraphs.install +++ b/paragraphs.install @@ -206,3 +206,10 @@ function paragraphs_update_8011() { $storage_definition->setRevisionable(TRUE); \Drupal::entityDefinitionUpdateManager()->updateFieldStorageDefinition($storage_definition); } + +/** + * Install file module. + */ +function paragraphs_update_8012() { + \Drupal::service('module_installer')->install(['file']); +} diff --git a/paragraphs.libraries.yml b/paragraphs.libraries.yml index bb2015d..b364ead 100644 --- a/paragraphs.libraries.yml +++ b/paragraphs.libraries.yml @@ -16,3 +16,9 @@ drupal.paragraphs.widget: css: theme: css/paragraphs.widget.css: {} + +drupal.paragraphs.list_builder: + version: VERSION + css: + theme: + css/paragraphs.list-builder.css: {} diff --git a/src/Controller/ParagraphsTypeListBuilder.php b/src/Controller/ParagraphsTypeListBuilder.php index 9b4a985..ff469cd 100644 --- a/src/Controller/ParagraphsTypeListBuilder.php +++ b/src/Controller/ParagraphsTypeListBuilder.php @@ -14,8 +14,12 @@ class ParagraphsTypeListBuilder extends ConfigEntityListBuilder { * {@inheritdoc} */ public function buildHeader() { - $header['label'] = $this->t('Paragraphs types'); + $header['icon_file'] = [ + 'data' => $this->t('Icon'), + ]; + $header['label'] = $this->t('Label'); $header['id'] = $this->t('Machine name'); + return $header + parent::buildHeader(); } @@ -23,6 +27,16 @@ class ParagraphsTypeListBuilder extends ConfigEntityListBuilder { * {@inheritdoc} */ public function buildRow(EntityInterface $entity) { + $row['icon_file'] = ''; + if ($icon_url = $entity->getIconUrl()) { + $row['icon_file']['class'][] = 'paragraphs-type-icon'; + $row['icon_file']['data'] = [ + '#theme' => 'image', + '#uri' => $icon_url, + '#width' => 32, + '#height' => 32, + ]; + } $row['label'] = $entity->label(); $row['id'] = $entity->id(); // You probably want a few more properties here... @@ -43,4 +57,13 @@ class ParagraphsTypeListBuilder extends ConfigEntityListBuilder { return $operations; } + /** + * {@inheritdoc} + */ + public function render() { + $build = parent::render(); + $build['#attached']['library'][] = 'paragraphs/drupal.paragraphs.list_builder'; + return $build; + } + } diff --git a/src/Entity/ParagraphsType.php b/src/Entity/ParagraphsType.php index f00f5a3..fad4944 100644 --- a/src/Entity/ParagraphsType.php +++ b/src/Entity/ParagraphsType.php @@ -31,6 +31,7 @@ use Drupal\paragraphs\ParagraphsTypeInterface; * config_export = { * "id", * "label", + * "icon_uuid", * "behavior_plugins", * }, * bundle_of = "paragraph", @@ -58,6 +59,13 @@ class ParagraphsType extends ConfigEntityBundleBase implements ParagraphsTypeInt public $label; /** + * UUID of the paragraphs type icon file. + * + * @var string + */ + protected $icon_uuid; + + /** * The paragraphs type behavior plugins configuration keyed by their id. * * @var array @@ -75,6 +83,23 @@ class ParagraphsType extends ConfigEntityBundleBase implements ParagraphsTypeInt /** * {@inheritdoc} */ + public function getIconFile() { + if ($this->icon_uuid) { + $files = $this->entityTypeManager() + ->getStorage('file') + ->loadByProperties(['uuid' => $this->icon_uuid]); + + if ($files) { + return array_shift($files); + } + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ public function getBehaviorPlugins() { if (!isset($this->behaviorCollection)) { $this->behaviorCollection = new ParagraphsBehaviorCollection(\Drupal::service('plugin.manager.paragraphs.behavior'), $this->behavior_plugins); @@ -85,6 +110,17 @@ class ParagraphsType extends ConfigEntityBundleBase implements ParagraphsTypeInt /** * {@inheritdoc} */ + public function getIconUrl() { + if ($image = $this->getIconFile()) { + return file_create_url($image->getFileUri()); + } + + return FALSE; + } + + /** + * {@inheritdoc} + */ public function getBehaviorPlugin($instance_id) { return $this->getBehaviorPlugins()->get($instance_id); } @@ -92,6 +128,20 @@ class ParagraphsType extends ConfigEntityBundleBase implements ParagraphsTypeInt /** * {@inheritdoc} */ + public function calculateDependencies() { + parent::calculateDependencies(); + + // Add the file icon entity as dependency if a UUID was specified. + if ($this->icon_uuid && $file_icon = $this->getIconFile()) { + $this->addDependency($file_icon->getConfigDependencyKey(), $file_icon->getConfigDependencyName()); + } + + return $this->dependencies; + } + + /** + * {@inheritdoc} + */ public function getEnabledBehaviorPlugins() { return $this->getBehaviorPlugins()->getEnabled(); } diff --git a/src/Form/ParagraphsTypeForm.php b/src/Form/ParagraphsTypeForm.php index d175519..e832b33 100644 --- a/src/Form/ParagraphsTypeForm.php +++ b/src/Form/ParagraphsTypeForm.php @@ -79,6 +79,19 @@ class ParagraphsTypeForm extends EntityForm { '#disabled' => !$paragraphs_type->isNew(), ); + $form['icon_file'] = [ + '#title' => $this->t('Paragraph type icon'), + '#type' => 'managed_file', + '#upload_location' => 'public://paragraphs_type_icon/', + '#upload_validators' => [ + 'file_validate_extensions' => ['png jpg svg'], + ], + ]; + + if ($file = $this->entity->getIconFile()) { + $form['icon_file']['#default_value'] = ['target_id' => $file->id()]; + } + // Loop over the plugins that can be applied to this paragraph type. if ($behavior_plugin_definitions = $this->paragraphsBehaviorManager->getApplicableDefinitions($paragraphs_type)) { $form['message'] = [ @@ -130,6 +143,15 @@ class ParagraphsTypeForm extends EntityForm { $paragraphs_type = $this->entity; + $icon_fild = $form_state->getValue(['icon_file', '0']); + // Set the file UUID to the paragraph configuration. + if (!empty($icon_fild) && $file = $this->entityTypeManager->getStorage('file')->load($icon_fild)) { + $paragraphs_type->set('icon_uuid', $file->uuid()); + } + else { + $paragraphs_type->set('icon_uuid', NULL); + } + if ($behavior_plugin_definitions = $this->paragraphsBehaviorManager->getApplicableDefinitions($paragraphs_type)) { foreach ($behavior_plugin_definitions as $id => $behavior_plugin_definition) { // Only validate if the plugin is enabled and has settings. diff --git a/src/ParagraphsTypeInterface.php b/src/ParagraphsTypeInterface.php index 9465772..0760107 100644 --- a/src/ParagraphsTypeInterface.php +++ b/src/ParagraphsTypeInterface.php @@ -37,13 +37,29 @@ interface ParagraphsTypeInterface extends ConfigEntityInterface { public function getEnabledBehaviorPlugins(); /** + * Returns the icon file entity. + * + * @return \Drupal\file\FileInterface|bool + * The icon's file entity or FALSE if icon does not exist. + */ + public function getIconFile(); + + /** + * Returns the icon's URL. + * + * @return string|bool + * The icon's URL or FALSE if icon does not exits. + */ + public function getIconUrl(); + + /** * Returns TRUE if $plugin_id is enabled on this ParagraphType Entity. * * @param string $plugin_id * The plugin id, as specified in the plugin annotation details. * * @return bool - * True or False dependant on plugin state + * TRUE if the plugin is enabled, FALSE otherwise. */ public function hasEnabledBehaviorPlugin($plugin_id); diff --git a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php index 097984a..c2c9fb2 100644 --- a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php @@ -634,9 +634,6 @@ class ParagraphsWidget extends WidgetBase { // Build the behavior plugins fields. $paragraphs_type = $paragraphs_entity->getParagraphType(); if ($paragraphs_type) { - $element['behavior_plugins'] = [ - '#weight' => -10, - ]; foreach ($paragraphs_type->getEnabledBehaviorPlugins() as $plugin_id => $plugin) { $element['behavior_plugins'][$plugin_id] = []; $subform_state = SubformState::createForSubform($element['behavior_plugins'][$plugin_id], $form, $form_state); diff --git a/src/Tests/Classic/ParagraphsTypesTest.php b/src/Tests/Classic/ParagraphsTypesTest.php index 185bb94..f837306 100644 --- a/src/Tests/Classic/ParagraphsTypesTest.php +++ b/src/Tests/Classic/ParagraphsTypesTest.php @@ -2,6 +2,8 @@ namespace Drupal\paragraphs\Tests\Classic; +use Drupal\paragraphs\Entity\ParagraphsType; + /** * Tests paragraphs types. * @@ -39,4 +41,36 @@ class ParagraphsTypesTest extends ParagraphsTestBase { } + /** + * Tests the paragraph type icon settings. + */ + public function testParagraphTypeIcon() { + $admin_user = $this->drupalCreateUser(['administer paragraphs types']); + $this->drupalLogin($admin_user); + // Add the paragraph type with icon. + $this->drupalGet('admin/structure/paragraphs_type/add'); + $this->assertText('Paragraph type icon'); + $test_files = $this->drupalGetTestFiles('image'); + $fileSystem = \Drupal::service('file_system'); + $edit = [ + 'label' => 'Test paragraph type', + 'id' => 'test_paragraph_type_icon', + 'files[icon_file]' => $fileSystem->realpath($test_files[0]->uri), + ]; + $this->drupalPostForm(NULL, $edit, t('Save and manage fields')); + $this->assertText('Saved the Test paragraph type Paragraphs type.'); + + // Check if the icon has been saved. + $this->drupalGet('admin/structure/paragraphs_type'); + $this->assertRaw('image-test.png'); + $this->clickLink('Edit'); + $this->assertText('image-test.png'); + + // Tests calculateDependencies method. + $paragraph_type = ParagraphsType::load('test_paragraph_type_icon'); + $dependencies = $paragraph_type->getDependencies(); + $dependencies_uuid[] = explode(':', $dependencies['content'][0]); + $this->assertEqual($paragraph_type->get('icon_uuid'), $dependencies_uuid[0][2]); + } + }