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/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..86f98ed 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_uuid'] = [ + 'data' => $this->t('Icon'), + ]; + $header['label'] = $this->t('Label'); $header['id'] = $this->t('Machine name'); + return $header + parent::buildHeader(); } @@ -23,6 +27,17 @@ 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, + '#alt' => $this->t('Icon for the @label paragraph type.', ['@label' => $entity->label()]), + '#width' => 32, + '#height' => 32, + ]; + } $row['label'] = $entity->label(); $row['id'] = $entity->id(); // You probably want a few more properties here... @@ -43,4 +58,12 @@ 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..6ddb003 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,16 @@ class ParagraphsType extends ConfigEntityBundleBase implements ParagraphsTypeInt /** * {@inheritdoc} */ + public function getIconFile() { + if ($this->icon_uuid) { + return \Drupal::service('entity.repository') + ->loadEntityByUuid('file', $this->icon_uuid); + } + } + + /** + * {@inheritdoc} + */ public function getBehaviorPlugins() { if (!isset($this->behaviorCollection)) { $this->behaviorCollection = new ParagraphsBehaviorCollection(\Drupal::service('plugin.manager.paragraphs.behavior'), $this->behavior_plugins); @@ -85,6 +103,15 @@ class ParagraphsType extends ConfigEntityBundleBase implements ParagraphsTypeInt /** * {@inheritdoc} */ + public function getIconUrl() { + if ($image = $this->getIconFile()) { + return file_create_url($image->getFileUri()); + } + } + + /** + * {@inheritdoc} + */ public function getBehaviorPlugin($instance_id) { return $this->getBehaviorPlugins()->get($instance_id); } @@ -92,6 +119,20 @@ class ParagraphsType extends ConfigEntityBundleBase implements ParagraphsTypeInt /** * {@inheritdoc} */ + public function calculateDependencies() { + parent::calculateDependencies(); + + // Add the file icon entity as dependency if an UUID was specified. + if ($this->icon_uuid && $file_icon = \Drupal::service('entity.repository')->loadEntityByUuid('file', $this->icon_uuid)) { + $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..725e656 100644 --- a/src/Form/ParagraphsTypeForm.php +++ b/src/Form/ParagraphsTypeForm.php @@ -79,6 +79,20 @@ class ParagraphsTypeForm extends EntityForm { '#disabled' => !$paragraphs_type->isNew(), ); + $form['icon_file'] = [ + '#title' => $this->t('Paragraph type icon'), + '#type' => 'managed_file', + '#description' => $this->t('Icon for he paragraph type.'), + '#upload_location' => 'public://paragraphs_type_icon/', + '#upload_validators' => [ + 'file_validate_extensions' => ['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 +144,18 @@ class ParagraphsTypeForm extends EntityForm { $paragraphs_type = $this->entity; + $icon_fid = $form_state->getValue(['icon_file', '0']); + // If a file was uploaded to be used as the icon, get its UUID to be stored + // in the config entity. + if (!empty($icon_fid) && $file = $this->entityTypeManager->getStorage('file')->load($icon_fid)) { + $paragraphs_type->set('icon_uuid', $file->uuid()); + } + else { + $paragraphs_type->set('icon_uuid', NULL); + } + + $status = $paragraphs_type->save(); + 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..d5eb500 100644 --- a/src/ParagraphsTypeInterface.php +++ b/src/ParagraphsTypeInterface.php @@ -37,13 +37,32 @@ interface ParagraphsTypeInterface extends ConfigEntityInterface { public function getEnabledBehaviorPlugins(); /** + * Returns the icon file. + * + * @return \Drupal\file\FileInterface + * The file entity of the button icon. + */ + public function getIconFile(); + + /** + * Returns the URL of the's icon. + * + * If no icon file is associated with this Embed Button entity, the embed type + * plugin's default icon is used. + * + * @return string + * The URL of the button icon. + */ + 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/Tests/Classic/ParagraphsTypesTest.php b/src/Tests/Classic/ParagraphsTypesTest.php index 185bb94..83d2500 100644 --- a/src/Tests/Classic/ParagraphsTypesTest.php +++ b/src/Tests/Classic/ParagraphsTypesTest.php @@ -1,6 +1,8 @@ drupalCreateUser(['administer paragraphs types']); + $this->drupalLogin($admin_user); + // Add the paragraph type with icon. + $this->drupalGet('admin/structure/paragraphs_type'); + $this->clickLink(t('Add paragraphs type')); + $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['1']->uri), + ]; + $this->drupalPostForm(NULL, $edit, t('Save and manage fields')); + $this->assertText('Saved the Test paragraph type Paragraphs type.'); + + // Check if icon is saved. + $this->drupalGet('admin/structure/paragraphs_type'); + // If alt attribute is presented image tag is also presented. + $this->assertTrue((bool) $this->xpath("//img[@alt='Icon for the Test paragraph type paragraph type.']"), 'Alt text found'); + $this->drupalGet('admin/structure/paragraphs_type'); + $this->clickLink('Edit'); + $this->assertText('image-test-transparent-indexed.gif'); + + // 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]); + } + }