From 5c699568d1e39b19991a2e5994634a304886bcd1 Mon Sep 17 00:00:00 2001 From: Yan Loetzer Date: Mon, 27 Nov 2017 07:07:45 -0500 Subject: [PATCH] by yanniboi: Implement drag and drop for dashboard blocks. --- composer.json | 4 + .../contacts.contact_tab.crm_indiv.yml | 2 + .../contacts.contact_tab.crm_notes.yml | 1 + .../install/contacts.contact_tab.crm_org.yml | 2 + .../install/contacts.contact_tab.summary.yml | 1 + config/schema/contact_tab.schema.yml | 6 + contacts.module | 421 +++++++++++++++++- contacts.routing.yml | 45 ++ .../contacts-tab-content-stacked.html.twig | 10 +- src/ContactsTabManager.php | 49 +- src/Controller/DashboardController.php | 149 ++++++- src/Entity/ContactTab.php | 28 ++ src/Entity/ContactTabInterface.php | 18 + src/Form/ContactTabForm.php | 23 + src/Form/DashboardBlockConfigureForm.php | 259 +++++++++++ src/Plugin/Block/ContactsDashboardTabs.php | 38 +- src/Plugin/Block/ContactsEntity.php | 53 ++- templates/contacts-dash-manage.html.twig | 5 + templates/contacts-dash-tab-content.html.twig | 1 + templates/contacts-dnd-card.html.twig | 59 +++ templates/contacts-hat.html.twig | 3 + 21 files changed, 1143 insertions(+), 34 deletions(-) create mode 100644 src/Form/DashboardBlockConfigureForm.php create mode 100644 templates/contacts-dash-manage.html.twig create mode 100644 templates/contacts-dash-tab-content.html.twig create mode 100644 templates/contacts-dnd-card.html.twig create mode 100644 templates/contacts-hat.html.twig diff --git a/composer.json b/composer.json index 2986710..edc1a40 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,10 @@ "drupal/ctools": { "#2667652: Option to expose filters in block on views block display": "https://www.drupal.org/files/issues/ctools-option_to_expose-2667652-3.patch", "#2866323 & #2865709: Fatal error on empty entity reference relationship": "https://www.drupal.org/files/issues/2866323-fix_entity_relationship_with_no_value.patch" + }, + "drupal/profile": { + "#2887274: template_preprocess_profile() causes exception for unsaved profiles": "https://www.drupal.org/files/issues/2887274-2-fix_url_exception_on_unsaved_profiles.patch", + "#2887299: Profile roles access not checking the owner": "https://www.drupal.org/files/issues/2887299-fix_owner_role_access-assume_owner_access.patch" } } } diff --git a/config/install/contacts.contact_tab.crm_indiv.yml b/config/install/contacts.contact_tab.crm_indiv.yml index 686fee0..06b5f08 100644 --- a/config/install/contacts.contact_tab.crm_indiv.yml +++ b/config/install/contacts.contact_tab.crm_indiv.yml @@ -15,6 +15,8 @@ relationships: id: 'typed_data_entity_relationship:entity:user:profile_crm_indiv' name: profile_crm_indiv source: user +roles: + crm_indiv: crm_indiv blocks: contacts_entity_profile_crm_indiv: id: 'contacts_entity:profile-crm_indiv' diff --git a/config/install/contacts.contact_tab.crm_notes.yml b/config/install/contacts.contact_tab.crm_notes.yml index 5eedebf..68b7cdb 100644 --- a/config/install/contacts.contact_tab.crm_notes.yml +++ b/config/install/contacts.contact_tab.crm_notes.yml @@ -15,6 +15,7 @@ relationships: id: 'typed_data_entity_relationship:entity:user:profile_crm_notes' name: profile_crm_notes source: user +roles: { } blocks: contacts_entity_profile_crm_notes: id: 'contacts_entity:profile-crm_notes' diff --git a/config/install/contacts.contact_tab.crm_org.yml b/config/install/contacts.contact_tab.crm_org.yml index 83537a7..b240eab 100644 --- a/config/install/contacts.contact_tab.crm_org.yml +++ b/config/install/contacts.contact_tab.crm_org.yml @@ -15,6 +15,8 @@ relationships: id: 'typed_data_entity_relationship:entity:user:profile_crm_org' name: profile_crm_org source: user +roles: + crm_org: crm_org blocks: contacts_entity_profile_crm_org: id: 'contacts_entity:profile-crm_org' diff --git a/config/install/contacts.contact_tab.summary.yml b/config/install/contacts.contact_tab.summary.yml index ba3aa31..885ef40 100644 --- a/config/install/contacts.contact_tab.summary.yml +++ b/config/install/contacts.contact_tab.summary.yml @@ -25,6 +25,7 @@ relationships: id: 'typed_data_entity_relationship:entity:user:profile_crm_notes' name: profile_crm_notes source: user +roles: { } blocks: contacts_entity_profile_crm_indiv: id: 'contacts_entity:profile-crm_indiv' diff --git a/config/schema/contact_tab.schema.yml b/config/schema/contact_tab.schema.yml index fb8f6ea..ad8580d 100644 --- a/config/schema/contact_tab.schema.yml +++ b/config/schema/contact_tab.schema.yml @@ -25,6 +25,12 @@ contacts.contact_tab.*: sequence: type: contacts.relationship.[%name] label: 'Relationship' + roles: + type: sequence + label: 'Roles' + sequence: + type: string + label: 'Role' blocks: type: sequence label: 'Blocks' diff --git a/contacts.module b/contacts.module index c94f834..91d4675 100644 --- a/contacts.module +++ b/contacts.module @@ -27,10 +27,56 @@ function contacts_theme() { 'variables' => ['tabs' => []], 'template' => 'contacts-dash-tabs', ], + 'contacts_dash_tab_content' => [ + 'variables' => [ + 'content' => [], + 'attributes' => [], + 'region_attributes' => [], + 'manage_mode' => NULL, + 'subpage' => NULL, + ], + 'template' => 'contacts-dash-tab-content', + ], 'contacts_dash_summary' => [ 'variables' => ['user' => NULL], 'template' => 'contacts-dash-summary', ], + 'contacts_dash_manage' => [ + 'variables' => [ + 'content' => [], + 'tabs' => [], + 'types' => [], + 'attributes' => [], + 'user' => NULL, + 'subpage' => NULL, + ], + 'template' => 'contacts-dash-manage', + ], + 'contacts_dnd_card' => [ + 'variables' => [ + 'id' => NULL, + 'mode' => NULL, + 'user' => NULL, + 'subpage' => NULL, + 'block' => [], + 'configure_link' => NULL, + 'attributes' => [], + 'label' => '', + 'description' => '', + 'profiles' => '', + 'tabs' => '', + 'link' => [], + ], + 'template' => 'contacts-dnd-card', + ], + 'contacts_hat' => [ + 'variables' => [ + 'role' => NULL, + 'hide_label' => NULL, + 'hide_button' => NULL, + ], + 'template' => 'contacts-hat', + ], 'user__contacts_dashboard_row' => [ 'base hook' => 'user', 'template' => 'user--contacts-dashboard-row', @@ -80,6 +126,254 @@ function template_preprocess_contact_tab_content(array &$variables) { $variables['region_attributes'] = new Attribute($variables['region_attributes']); } +/** + * Prepares variables for contact dashboard tab content. + * + * Default template: contacts-dash-tab-content.html.twig. + * + * @param array $variables + * An associative array containing: + * - attributes: HTML markup attributes for the content wrapper. + * - region_attributes: HTML markup attributes for the content wrapper. + */ +function template_preprocess_contacts_dash_tab_content(array &$variables) { + $variables['messages'] = $variables['content']['messages']; + $variables['region_attributes'] = new Attribute($variables['region_attributes']); + /* @var \Drupal\contacts\Entity\ContactTab $tab */ + $tab = \Drupal::service('contacts.tab_manager')->getTabByPath(NULL, $variables['subpage']); + + if ($variables['manage_mode']) { + $variables['tab'] = $tab->id(); + + $roles = $tab->getRoles(); + + // Show required hats for tab. + foreach ($roles as $role) { + $variables['header']['hats'][] = [ + '#theme' => 'contacts_hat', + '#role' => $role, + ]; + } + } + + $layout = $tab->get('layout') ?: 'contacts_tab_content.stacked'; + + // @todo switch to core layout manager plugin.manager.core.layout? + /* @var \Drupal\layout_plugin\Plugin\Layout\LayoutPluginManager $layoutPluginManager */ + $layoutPluginManager = \Drupal::service('plugin.manager.layout_plugin'); + /* @var \Drupal\layout_plugin\Plugin\Layout\LayoutInterface $layoutInstance */ + $layoutInstance = $layoutPluginManager->createInstance($layout, []); + + $regions = []; + + foreach ($layoutInstance->getPluginDefinition()['regions'] as $key => $region) { + if (!empty($variables['content'][$key])) { + $regions[$key] = $variables['content'][$key]; + } + } + + $variables['content'] = $layoutInstance->build($regions); + $variables['content']['#region_attributes'] = $variables['region_attributes']; +} + +/** + * Prepares variables for contact tab content stacked layout. + * + * Default template: contacts-tab-content-stacked.html.twig. + * + * @see template_preprocess_contacts_dash_tab_content(). + */ +function template_preprocess_contacts_tab_content_stacked(array &$variables) { + $content = $variables['content']; + $variables['region_attributes'] = $content['#region_attributes'] ?: []; +} + +/** + * Prepares variables for a contacts hat icon. + * + * Default template: contacts-hat.html.twig. + * + * @param array $variables + * An associative array containing: + * - attributes: HTML markup attributes for the content wrapper. + * - role: The machine name for the role to be rendered. + */ +function template_preprocess_contacts_hat(array &$variables) { + $role = \Drupal::entityTypeManager()->getStorage('user_role')->load($variables['role']); + $variables['label'] = $role->label(); + $variables['color'] = '#' . $role->getThirdPartySetting('contacts', 'user_role_color') ?: 'blue'; + $variables['icon'] = 'fa-' . $role->getThirdPartySetting('contacts', 'user_role_icon') ?: 'fa-user'; +} + +/** + * Prepares variables for contacts drag and drop cards. + * + * Default template: contacts-dnd-card.html.twig. + * + * @param array $variables + * An associative array containing: + * - attributes: HTML markup attributes for the content wrapper. + * - region_attributes: HTML markup attributes for the content wrapper. + */ +function template_preprocess_contacts_dnd_card(array &$variables) { + /* @var \Drupal\contacts\ContactsTabManager $tab_manager */ + $tab_manager = \Drupal::service('contacts.tab_manager'); + + /* @var \Drupal\Core\Block\BlockManager $block_manager */ + $block_manager = \Drupal::service('plugin.manager.block'); + $definition = $block_manager->getDefinition($variables['id']); + + /* @var \Drupal\Core\Entity\EntityTypeManager $entity_type_manager */ + $entity_type_manager = \Drupal::service('entity_type.manager'); + + list($variables['block_plugin'], $variables['identifier'], $variables['bundle']) = preg_split( "/(:|-)/", $variables['id']); + + $variables['attributes']['data-dnd-contacts-block-id'] = "{$variables['block_plugin']}_{$variables['identifier']}_{$variables['bundle']}"; + $variables['attributes']['data-dnd-block-mode'] = $variables['mode']; + switch ($variables['mode']) { + case 'meta': + switch ($variables['block_plugin']) { + case 'contacts_entity': + $entity_type = $entity_type_manager->getDefinition($variables['identifier']); + + if ($entity_type->getBundleEntityType()) { + $bundle_storage = $entity_type_manager->getStorage($entity_type->getBundleEntityType()); + $bundle_entity = $bundle_storage->load($variables['bundle']); + $variables['label'] = $bundle_entity->label(); + $variables['content']['description'] = "{$entity_type->get('label')} entity for {$bundle_entity->label()}."; + + // @todo find out how to abstract this. + if ($variables['identifier'] == 'profile' && !empty(array_filter($bundle_entity->getRoles()))) { + $roles = user_roles(); + uasort($roles, 'contacts_sort_roles'); + $roles = array_intersect(array_keys($roles), $bundle_entity->getRoles()); + $hats = []; + // @todo Show hat icons instead of labels. + foreach ($roles as $role) { + $hats[] = [ + '#theme' => 'contacts_hat', + '#role' => $role, + '#hide_label' => TRUE, + ]; + } + $variables['content']['needed_hats'] = $hats; + $variables['content']['links'] = [ + 'fields' => [ + 'text' => 'Manage profile fields', + 'url' => "/admin/config/people/profiles/types/manage/{$variables['bundle']}/fields", + ], + ]; + } + } + else { + $variables['label'] = $entity_type->get('label'); + $variables['content']['description'] = "{$entity_type->get('label')} entity."; + } + + $variables['attributes']['data-dnd-contacts-entity-type'] = $variables['entity'] = $variables['identifier']; + $variables['attributes']['data-dnd-contacts-entity-bundle'] = $variables['bundle']; + $variables['attributes']['data-dnd-contacts-entity-relationship'] = "{$variables['identifier']}_{$variables['bundle']}"; + break; + + case 'views_block': + $view = $entity_type_manager->getStorage('view')->load($variables['identifier']); + $variables['label'] = $view->get('label'); + + $displays = $view->get('display'); + if (!empty($displays['default']['display_options']['title'])) { + $variables['label'] = $displays['default']['display_options']['title']; + } + if (!empty($displays[$variables['bundle']]['display_options']['title'])) { + $variables['label'] = $displays[$variables['bundle']]['display_options']['title']; + } + + $variables['content']['description'] = $view->get('description'); + $variables['attributes']['data-dnd-contacts-entity-type'] = $variables['entity'] = 'view'; + $variables['attributes']['data-dnd-contacts-entity-bundle'] = "{$variables['identifier']}:{$variables['bundle']}"; + $variables['content']['links'] = [ + 'edit' => [ + 'text' => 'Edit View', + 'url' => "/admin/structure/views/view/{$variables['identifier']}/edit/{$variables['bundle']}", + ], + ]; + break; + } + + $variables['content']['placed_tabs'] = $tab_manager->getTabsWithBlock("{$variables['block_plugin']}_{$variables['identifier']}_{$variables['bundle']}"); + $variables['content']['link'] = []; + break; + + case 'manage': + $variables['pseudo_content'] = TRUE; + $variables['configure_link'] = "/admin/contacts-ajax/manage-configure-block/{$variables['user']}/{$variables['subpage']}/{$variables['attributes']['data-dnd-contacts-block-id']}"; + + switch ($variables['block_plugin']) { + case 'contacts_entity': + $variables['label'] = $variables['block']->label(FALSE); + $entity_type = $entity_type_manager->getDefinition($variables['identifier']); + $variables['entity'] = $entity_type; + $variables['attributes']['data-dnd-contacts-entity-type'] = $variables['entity'] = $variables['identifier']; + $variables['attributes']['data-dnd-contacts-entity-bundle'] = $variables['bundle']; + + if ($entity_type->getBundleEntityType()) { + $bundle_storage = $entity_type_manager->getStorage($entity_type->getBundleEntityType()); + $bundle_entity = $bundle_storage->load($variables['bundle']); + + // @todo find out how to abstract this. + if ($variables['identifier'] == 'profile' && !empty(array_filter($bundle_entity->getRoles()))) { + $roles = array_filter($bundle_entity->getRoles()); + $hats = []; + // @todo Show hat icons instead of labels. + foreach ($roles as $role) { + $hats[] = [ + '#theme' => 'contacts_hat', + '#role' => $role, + ]; + } + $variables['footer']['visible_hats'] = $hats; + + $variables['content']['links'] = [ + 'fields' => [ + 'text' => 'Manage profile fields', + 'url' => "/admin/config/people/profiles/types/manage/{$variables['bundle']}/fields", + ], + 'view-modes' => [ + 'text' => 'Manage display', + 'url' => "/admin/config/people/profiles/types/manage/{$variables['bundle']}/display", + ], + ]; + } + } + + break; + + case 'views_block': +// $view = $entity_type_manager->getStorage('view')->load($variables['identifier']); + $variables['label'] = $variables['block']->label(); + $variables['entity'] = 'view'; + $variables['attributes']['data-dnd-contacts-entity-type'] = $variables['entity'] = 'view'; + $variables['attributes']['data-dnd-contacts-entity-bundle'] = "{$variables['identifier']}_{$variables['bundle']}"; + $variables['content']['links'] = [ + 'edit' => [ + 'text' => 'Edit View', + 'url' => "/admin/structure/views/view/{$variables['identifier']}/edit/{$variables['bundle']}", + ], + ]; + break; + } + break; + + case 'configure': + $variables['label'] = 'Configure block'; + + $form = \Drupal::formBuilder()->getForm(\Drupal\contacts\Form\DashboardBlockConfigureForm::class, $variables['block']); + $variables['content']['configure_form'] = $form; + break; + } + + $variables['attributes'] = new Attribute($variables['attributes']); +} + /** * Prepares variables for contact dashboard summary block. * @@ -158,6 +452,17 @@ function template_preprocess_contacts_dash_summary(array &$variables) { $variables['image']['#uri'] = 'contacts://images/default-indiv.png'; } } + $roles = user_roles(); + uasort($roles, 'contacts_sort_roles'); + $roles = array_intersect(array_keys($roles), $user->getRoles()); + $variables['hats'] = []; + + foreach ($roles as $role) { + $variables['hats'][] = [ + '#theme' => 'contacts_hat', + '#role' => $role, + ]; + } } /** @@ -242,10 +547,16 @@ function contacts_preprocess_user(&$variables) { $roles = user_roles(); uasort($roles, 'contacts_sort_roles'); - $roles = array_map(function ($item) { - return $item->label(); - }, $roles); - $variables['roles'] = implode(', ', array_intersect_key($roles, array_fill_keys($user->getRoles(), TRUE))); + $roles = array_intersect(array_keys($roles), $user->getRoles()); + + foreach ($roles as $role) { + $variables['roles'][] = [ + '#theme' => 'contacts_hat', + '#role' => $role, + '#hide_label' => TRUE, + '#hide_button' => TRUE, + ]; + } // For individuals. if ($user->hasRole('crm_indiv')) { @@ -280,6 +591,56 @@ function contacts_preprocess_user(&$variables) { } } +/** + * Implements hook_form_alter(). + */ +function contacts_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { + if ($form_id === 'user_role_form') { + /* @var \Drupal\user\Entity\Role $role */ + $role = $form_state->getFormObject()->getEntity(); + $form['user_role_color'] = array( + '#type' => 'select', + '#title' => t('Role Color'), + '#description' => t('Choose a 6 digit hex color to visually represent this role.'), + '#field_prefix' => '#', + '#options' => contacts_get_hat_palette(), + '#default_value' => $role->getThirdPartySetting('contacts', 'user_role_color'), + '#weight' => 1, + ); + + $form['user_role_icon'] = array( + '#type' => 'textfield', + '#title' => t('Role Icon'), + '#description' => t('Enter the class for the font awesome icon you would like to use.') . '
http://fontawesome.io/icons/', + '#field_prefix' => 'fa-', + '#default_value' => $role->getThirdPartySetting('contacts', 'user_role_icon'), + '#weight' => 2, + '#size' => 20, + ); + + $form['#entity_builders'][] = 'contacts_form_user_role_add_form_builder'; + } +} + +/** + * Entity builder for the menu configuration entity. + */ +function contacts_form_user_role_add_form_builder($entity_type, \Drupal\user\Entity\Role $role, &$form, \Drupal\Core\Form\FormStateInterface $form_state) { + if ($form_state->getValue('user_role_color')) { + $role->setThirdPartySetting('contacts', 'user_role_color', $form_state->getValue('user_role_color')); + } + else { + $role->unsetThirdPartySetting('contacts', 'user_role_color'); + } + + if ($form_state->getValue('user_role_icon')) { + $role->setThirdPartySetting('contacts', 'user_role_icon', $form_state->getValue('user_role_icon')); + } + else { + $role->unsetThirdPartySetting('contacts', 'user_role_icon'); + } +} + /** * Sort roles by CRM then alphabetically. * @@ -359,6 +720,8 @@ function contacts_panels_build_alter(array &$build, PanelsDisplayVariant $panels * Allow dashboard placement of views blocks if they have the correct tag. */ function contacts_block_alter(&$definitions) { + // Since menu blocks use derivatives, check the definition ID instead of + // relying on the plugin ID. foreach ($definitions as $key => &$definition) { if ($definition['id'] === 'views_block') { if (substr($key, 0, 12) == 'views_block:') { @@ -507,3 +870,53 @@ function contacts_preprocess_block(&$variables, $hook) { function contacts_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) { $form['account']['roles']['#default_value'][] = 'crm_indiv'; } + +/** + * Gets color palette options for the contacts hats. + * + * @return array + * Array of colors keyed by the hex code. + */ +function contacts_get_hat_palette() { + return [ + 'F59FB8' => 'Pixie', + 'E690A8' => 'Cotton Candy', + 'EC8086' => 'Smoothie', + 'D4687E' => 'Hollyhock', + 'B84543' => 'Cranberry', + 'B24F60' => 'Ruby', + '97544F' => 'Barn Red', + 'F49580' => 'Sorbet', + 'EE825A' => 'Sunset', + 'D98E4D' => 'Goldrush', + 'C77140' => 'Autumn Terracotta', + 'F5CD6D' => 'Canary', + 'FFCD2A' => 'Honey', + 'D3C28A' => 'Flaxen', + '97986B' => 'Olive', + 'C2CB93' => 'Sweet Leaf', + 'AAD272' => 'Pear', + '708C4C' => 'Topiary', + '636E4D' => 'New England Ivy', + '475F54' => 'Ponderosa Pine', + '88B6AD' => 'Juniper', + '9DCDCD' => 'Glacier', + '48B9B6' => 'Lagoon', + '84BCD5' => 'Crystal Blue', + '30789D' => 'Pacifica', + '6588A9' => 'Indian Corn Blue', + '435168' => 'Outdoor Denim', + 'B865AB' => 'Thistle', + '8F7CB2' => 'Gypsy', + '866A7D' => 'Smokey Plum', + 'D8C7A9' => 'Bamboo', + 'E6D1B8' => 'Champagne', + 'D0D1B8' => 'Cashmere', + 'A58869' => 'Desert Sand', + 'B1816C' => 'Saddle', + '896859' => 'Chocolate', + '756857' => 'Cocoa', + 'C0B9B4' => 'Whisper', + '9A9185' => 'Slate', + ]; +} diff --git a/contacts.routing.yml b/contacts.routing.yml index 89061de..0cdd8e3 100644 --- a/contacts.routing.yml +++ b/contacts.routing.yml @@ -9,6 +9,51 @@ contacts.ajax_subpage: requirements: _permission: 'view contacts' +contacts.ajax_manage_mode: + path: '/admin/contacts-ajax/manage-mode/{user}/{subpage}' + defaults: + _controller: '\Drupal\contacts\Controller\DashboardController::ajaxManageMode' + options: + parameters: + user: + type: entity:user + requirements: + _permission: 'view contacts' +# _csrf_token: 'TRUE' + +contacts.ajax_manage_mode_tab: + path: '/admin/contacts-ajax/manage-mode-tab/{user}/{subpage}/{tab}' + defaults: + _controller: '\Drupal\contacts\Controller\DashboardController::ajaxManageModeTab' + options: + parameters: + user: + type: entity:user + requirements: + _permission: 'view contacts' +# _csrf_token: 'TRUE' + +contacts.ajax_manage_mode_configure_block: + path: '/admin/contacts-ajax/manage-configure-block/{user}/{subpage}/{block}/{mode}' + defaults: + _controller: '\Drupal\contacts\Controller\DashboardController::ajaxManageModeConfigureBlock' + mode: 'configure' + options: + parameters: + user: + type: entity:user + requirements: + _permission: 'view contacts' +# _csrf_token: 'TRUE' + +contacts.ajax_update_blocks: + path: '/admin/contacts-ajax/update-layout' + defaults: + _controller: '\Drupal\contacts\Controller\DashboardController::updateBlocks' + requirements: + _permission: 'view contacts' +# _csrf_token: 'TRUE' + contacts.add_indiv_form: path: '/admin/contacts/add/indiv' defaults: diff --git a/layouts/tab_content_stacked/contacts-tab-content-stacked.html.twig b/layouts/tab_content_stacked/contacts-tab-content-stacked.html.twig index edd2c72..c7ae1f1 100644 --- a/layouts/tab_content_stacked/contacts-tab-content-stacked.html.twig +++ b/layouts/tab_content_stacked/contacts-tab-content-stacked.html.twig @@ -1,14 +1,14 @@ -
-
+
+
{{ content.top }}
-
+
{{ content.left }}
-
+
{{ content.right }}
-
+
{{ content.bottom }}
diff --git a/src/ContactsTabManager.php b/src/ContactsTabManager.php index ff99c67..75a9757 100644 --- a/src/ContactsTabManager.php +++ b/src/ContactsTabManager.php @@ -113,24 +113,53 @@ public function getTab(UserInterface $contact, $id) { return NULL; } + /** + * Gets a list of tabs that have a block in them. + * + * @param string $block_id + * The id of the block being searched for. + * + * @return array + * Array of tab labels keyed by tab id. + */ + public function getTabsWithBlock($block_id) { + $tabs = $this->getTabs(); + + $found = []; + foreach ($tabs as $id => $tab) { + $blocks = $this->getBlocks($tab, NULL, FALSE); + if (in_array($block_id, array_keys($blocks))) { + $found[$id] = $tab->label(); + } + } + + return $found; + } + /** * {@inheritdoc} */ - public function getTabByPath(UserInterface $contact, $path) { + public function getTabByPath(UserInterface $contact = NULL, $path) { $tabs = $this->entityTypeManager->getStorage('contact_tab')->loadByProperties(['path' => $path]); $tab = reset($tabs); - if ($tab) { - return $tab; + if (!$tab) { + return NULL; } - return NULL; + // Check this tab is valid for the contact. + $manage_mode = \Drupal::state()->get('manage_mode'); + if ($contact && !$manage_mode && !$this->verifyTab($tab, $contact)) { + return NULL; + } + + return $tab; } /** * {@inheritdoc} */ - public function getTabs(UserInterface $contact) { + public function getTabs(UserInterface $contact = NULL) { // Load all our active tabs. /* @var \Drupal\contacts\Entity\ContactTabInterface[] $tabs */ $tabs = $this->entityTypeManager->getStorage('contact_tab')->loadByProperties(['status' => TRUE]); @@ -140,9 +169,17 @@ public function getTabs(UserInterface $contact) { // Remove any tabs that are not for this contact. foreach ($tabs as $id => $tab) { - if (!$this->verifyTab($tab, $contact)) { + $manage_mode = \Drupal::state()->get('manage_mode'); + + if ($contact && !$manage_mode && !$this->verifyTab($tab, $contact)) { unset($tabs[$id]); } + + if ($contact && !empty($tab->getRoles())) { + if (empty(array_intersect($contact->getRoles(), $tab->getRoles()))) { + unset($tabs[$id]); + } + } } // Sort our tabs by weight. diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index 913efe5..e1a86c7 100644 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -6,6 +6,8 @@ use Drupal\contacts\ContactsTabManager; use Drupal\contacts\Entity\ContactTab; use Drupal\Core\Ajax\HtmlCommand; +use Drupal\Core\Ajax\ReplaceCommand; +use Drupal\Core\Ajax\SettingsCommand; use Drupal\Core\Block\BlockManager; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Ajax\AjaxResponse; @@ -56,6 +58,118 @@ public static function create(ContainerInterface $container) { ); } + + /** + * Return the AJAX command for changing tab. + * + * @param \Drupal\user\UserInterface $user + * The user we are viewing. + * @param string $subpage + * The subpage we want to view. + * @param boolean|null $manage_mode + * The user we are viewing. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The response commands. + */ + public function ajaxManageMode($user, $subpage, $manage_mode = NULL) { + if (is_null($manage_mode)) { + // Toggle manage mode. + $manage_mode = \Drupal::state()->get('manage_mode'); + } + + \Drupal::state()->set('manage_mode', !$manage_mode); + + return $this->ajaxTab($user, $subpage); + } + + /** + * Return the AJAX command for changing tab. + * + * @param \Drupal\user\UserInterface $user + * The user we are viewing. + * @param string $subpage + * The subpage we want to view. + * @param string|null $tab + * The user we are viewing. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The response commands. + * + * @todo Combine this method with \Drupal\contacts\Plugin\Block\ContactsDashboardTabs::buildContent(). + */ + public function ajaxManageModeTab($user, $subpage, $tab = NULL) { + if (is_null($tab)) { + // Toggle manage mode. + \Drupal::state()->set('manage_mode_tab', 'blocks'); + } + else { + \Drupal::state()->set('manage_mode_tab', $tab); + } + + return $this->ajaxTab($user, $subpage); + } + + /** + * Return the AJAX command for changing tab. + * + * @param \Drupal\user\UserInterface $user + * The user we are viewing. + * @param string $subpage + * The subpage we want to view. + * @param string $block + * The user we are viewing. + * @param string $mode + * The mode to render the block for. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The response commands. + */ + public function ajaxManageModeConfigureBlock($user, $subpage, $block, $mode = 'configure') { + $tab = $this->tabManager->getTabByPath($user, $subpage); + if ($tab) { + $blocks = $this->tabManager->getBlocks($tab, $user); + + if (isset($blocks[$block])) { + $key = $block; + /* @var \Drupal\Core\Block\BlockPluginInterface $block */ + $block = $blocks[$key]; + + $block_content = [ + '#theme' => 'contacts_dnd_card', + '#attributes' => [ + 'class' => ['draggable', 'draggable-active', 'card'], + 'data-dnd-contacts-block-tab' => $tab->id(), + ], + '#id' => $block->getPluginId(), + '#block' => $block, + '#user' => $user->id(), + '#subpage' => $subpage, + '#mode' => $mode, + ]; + } + else { + drupal_set_message($this->t('Page not found.'), 'warning'); + + } + } + + else { + drupal_set_message($this->t('Page not found.'), 'warning'); + } + + // Create AJAX Response object. + $response = new AjaxResponse(); + + if (!empty($block_content)) { + $response->addCommand(new ReplaceCommand("div[data-dnd-contacts-block-id='{$key}'][data-dnd-block-mode!='meta']", $block_content)); + } + + // Return ajax response. + return $response; + + } + /** * Return the AJAX command for changing tab. * @@ -70,31 +184,48 @@ public static function create(ContainerInterface $container) { * @todo Combine this method with \Drupal\contacts\Plugin\Block\ContactsDashboardTabs::buildContent(). */ public function ajaxTab(UserInterface $user, $subpage) { + $manage_mode = \Drupal::state()->get('manage_mode'); + $url = Url::fromRoute('page_manager.page_view_contacts_dashboard_contact', [ 'user' => $user->id(), 'subpage' => $subpage, ]); $content = [ - '#type' => 'container', - 'content' => [ - '#type' => 'contact_tab_content', - '#attributes' => ['class' => ['dash-content']], - '#subpage' => $subpage, - '#user' => $user, - '#tab' => $this->tabManager->getTabByPath($user, $subpage), - ], + '#theme' => 'contacts_dash_tab_content', + '#region_attributes' => ['class' => ['drag-area']], + '#subpage' => $subpage, + '#manage_mode' => $manage_mode, + '#content' => [], ]; + $content['#attached']['drupalSettings']['dragMode'] = $manage_mode; + + if ($manage_mode) { + $content['#region_attributes']['class'][] = 'show'; + } // Prepend the content with system messages. - $content['messages'] = [ + $content['#content']['messages'] = [ '#type' => 'status_messages', '#weight' => -99, ]; + // Also update the manage sidebar content. + // @todo why does this break for empty tabs? + // Probably need to move sidebar out of theme. + if (function_exists('contacts_theme_dashboard_manage_sidebar_content')) { + $sidebar_content = contacts_theme_dashboard_manage_sidebar_content(); + } + // Create AJAX Response object. $response = new AjaxResponse(); $response->addCommand(new ContactsTab($subpage, $url->toString())); + $response->addCommand(new SettingsCommand(['dragMode' => $manage_mode], TRUE)); + + if (isset($sidebar_content)) { + $response->addCommand(new HtmlCommand('#sidebar-manage-content', $sidebar_content['content'])); + } + $response->addCommand(new HtmlCommand('#contacts-tabs-content', $content)); // Return ajax response. diff --git a/src/Entity/ContactTab.php b/src/Entity/ContactTab.php index 59c1f43..a77eef6 100644 --- a/src/Entity/ContactTab.php +++ b/src/Entity/ContactTab.php @@ -13,6 +13,7 @@ * handlers = { * "list_builder" = "Drupal\contacts\ContactTabListBuilder", * "form" = { + * "add" = "Drupal\contacts\Form\ContactTabForm", * "edit" = "Drupal\contacts\Form\ContactTabForm", * "delete" = "Drupal\contacts\Form\ContactTabDeleteForm" * }, @@ -29,6 +30,7 @@ * }, * links = { * "canonical" = "/admin/structure/contact-tabs/{contact_tab}", + * "add-form" = "/admin/structure/contact-tabs/add", * "edit-form" = "/admin/structure/contact-tabs/{contact_tab}/edit", * "delete-form" = "/admin/structure/contact-tabs/{contact_tab}/delete", * "collection" = "/admin/structure/contact-tabs" @@ -77,6 +79,17 @@ class ContactTab extends ConfigEntityBase implements ContactTabInterface { */ protected $relationships = []; + /** + * The roles required for the tab. + * + * An array including: + * - id: The relationship plugin id. + * - name: The name for the context. + * + * @var array + */ + protected $roles = []; + /** * The blocks configuration. * @@ -125,6 +138,21 @@ public function setRelationships(array $relationships) { return $this; } + /** + * {@inheritdoc} + */ + public function getRoles() { + return $this->roles; + } + + /** + * {@inheritdoc} + */ + public function setRoles(array $roles) { + $this->roles = $roles; + return $this; + } + /** * {@inheritdoc} */ diff --git a/src/Entity/ContactTabInterface.php b/src/Entity/ContactTabInterface.php index 09ecdde..267dd8c 100644 --- a/src/Entity/ContactTabInterface.php +++ b/src/Entity/ContactTabInterface.php @@ -27,6 +27,24 @@ public function getPath(); */ public function setPath($path); + /** + * Get the roles required by the tab. + * + * @return array + * An array of role ids. + */ + public function getRoles(); + + /** + * Set the roles required by the tab. + * + * @param array $roles + * An array of role ids. + * + * @return $this + */ + public function setRoles(array $roles); + /** * Get the relationship definitions. * diff --git a/src/Form/ContactTabForm.php b/src/Form/ContactTabForm.php index cf13b2a..64953db 100644 --- a/src/Form/ContactTabForm.php +++ b/src/Form/ContactTabForm.php @@ -59,9 +59,32 @@ public function form(array $form, FormStateInterface $form_state) { 'standalone' => TRUE, ]; + $options = []; + $roles = user_roles(TRUE); + foreach ($roles as $id => $role) { + $options[$id] = $role->label(); + } + + $form['required_hats'] = [ + '#type' => 'checkboxes', + '#title' => $this->t('Require hats'), + '#description' => $this->t('Require that a user has at least one of these hats to show this tab.'), + '#options' => $options, + '#default_value' => array_keys($this->entity->getRoles()), + ]; + return $form; } + public function buildEntity(array $form, FormStateInterface $form_state) { + $entity = parent::buildEntity($form, $form_state); + + $required_hats = $form_state->getValue('required_hats'); + $entity->set('roles', array_filter($required_hats)); + + return $entity; + } + /** * {@inheritdoc} */ diff --git a/src/Form/DashboardBlockConfigureForm.php b/src/Form/DashboardBlockConfigureForm.php new file mode 100644 index 0000000..af638ed --- /dev/null +++ b/src/Form/DashboardBlockConfigureForm.php @@ -0,0 +1,259 @@ +entityTypeManager = $entity_type_manager; + $this->tabManager = $tab_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('contacts.tab_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return "dashboard_block_configure_form"; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $this->block = $form_state->getBuildInfo()['args'][0]; + + $form['#tree'] = TRUE; + $form['settings'] = []; + $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state); + $form['settings'] = $this->block->buildConfigurationForm($form['settings'], $subform_state);; + + list($block_plugin, $identifier, $bundle) = preg_split( "/(:|-)/", $this->block->getPluginId()); + $this->blockKey = "{$block_plugin}_{$identifier}_{$bundle}"; + $this->user = $this->getRouteMatch()->getParameter('user'); + $this->subpage = $this->getRouteMatch()->getParameter('subpage'); + $this->tab = $this->tabManager->getTabByPath($this->user, $this->subpage); + + if (isset($form['settings']['admin_label'])) { + unset($form['settings']['admin_label']); + } + + if (isset($form['settings']['context_mapping'])) { + unset($form['settings']['context_mapping']); + } + + $form['submit'] = [ + '#type' => 'submit', + '#value' => 'Save', + '#attributes' => ['class' => ['button--primary']], + '#ajax' => [ + 'callback' => [$this, 'updateBlock'], + 'progress' => [ + 'type' => 'throbber', + 'message' => "Updating...", + ], + ], + ]; + + $form['cancel'] = [ + '#type' => 'submit', + '#value' => 'Cancel', + '#submit' => [[$this, 'cancelBlock']], + '#limit_validation_errors' => [], + '#attributes' => [ + 'class' => ['btn', 'btn-outline-secondary'], + ], + '#ajax' => [ + 'callback' => [$this, 'updateBlock'], + 'progress' => [ + 'type' => 'throbber', + 'message' => "Updating...", + ], + ], + ]; + + $form['delete'] = [ + '#type' => 'submit', + '#submit' => [[$this, 'deleteBlock']], + '#value' => 'Delete', + '#attributes' => ['class' => ['btn-danger']], + '#ajax' => [ + 'callback' => [$this, 'removeBlock'], + 'progress' => [ + 'type' => 'throbber', + 'message' => "Updating...", + ], + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state); + $this->block->validateConfigurationForm($form['settings'], $sub_form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state); + $this->block->submitConfigurationForm($form, $sub_form_state); + $this->tab->setBlock($this->blockKey, $this->block->getConfiguration()); + $this->tab->save(); + } + + /** + * Form submission handler; removes block from tab. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function deleteBlock(array &$form, FormStateInterface $form_state) { + $blocks = $this->tab->getBlocks(); + unset($blocks[$this->blockKey]); + $this->tab->setBlocks($blocks); + $this->tab->save(); + } + + /** + * Form submission handler; removes block from tab. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function cancelBlock(array &$form, FormStateInterface $form_state) { + // Do nothing. + } + + /** + * AJAX response callback to update form markup. + * + * @param array $form + * Drupal form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Drupal form state. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * Ajax response to update markup. + */ + public function updateBlock(array $form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + + $block_content = [ + '#theme' => 'contacts_dnd_card', + '#attributes' => [ + 'class' => ['draggable', 'draggable-active', 'card'], + 'data-dnd-contacts-block-tab' => $this->tab->id(), + ], + '#id' => $this->block->getPluginId(), + '#block' => $this->block, + '#user' => $this->user->id(), + '#subpage' => $this->subpage, + '#mode' => 'manage', + ]; + + $response->addCommand(new ReplaceCommand("div[data-dnd-contacts-block-id='{$this->blockKey}'][data-dnd-block-mode!='meta']", $block_content)); + return $response; + } + + /** + * AJAX response callback to update form markup. + * + * @param array $form + * Drupal form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Drupal form state. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * Ajax response to update markup. + */ + public function removeBlock(array $form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + $response->addCommand(new RemoveCommand("div[data-dnd-contacts-block-id='{$this->blockKey}'][data-dnd-block-mode!='meta']")); + return $response; + } + +} \ No newline at end of file diff --git a/src/Plugin/Block/ContactsDashboardTabs.php b/src/Plugin/Block/ContactsDashboardTabs.php index 56c1a2c..1341f0f 100644 --- a/src/Plugin/Block/ContactsDashboardTabs.php +++ b/src/Plugin/Block/ContactsDashboardTabs.php @@ -6,6 +6,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Core\Url; +use Drupal\layout_plugin\Plugin\Layout\LayoutPluginManager; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Block\BlockBase; @@ -27,6 +28,13 @@ class ContactsDashboardTabs extends BlockBase implements ContextAwarePluginInter */ protected $tabManager; + /** + * The layout plugin manager. + * + * @var \Drupal\layout_plugin\Plugin\Layout\LayoutPluginManager + */ + protected $layoutManager; + /** * Whether we are building tabs via AJAX. * @@ -59,10 +67,15 @@ class ContactsDashboardTabs extends BlockBase implements ContextAwarePluginInter * The plugin implementation definition. * @param \Drupal\contacts\ContactsTabManager $tab_manager * The tab manager. + * @param \Drupal\layout_plugin\Plugin\Layout\LayoutPluginManager $layout_manager + * The layout plugin manager. + * + * @todo Switch to core layout manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, ContactsTabManager $tab_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, ContactsTabManager $tab_manager, LayoutPluginManager $layout_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->tabManager = $tab_manager; + $this->layoutManager = $layout_manager; $this->ajax = TRUE; } @@ -74,7 +87,8 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('contacts.tab_manager') + $container->get('contacts.tab_manager'), + $container->get('plugin.manager.layout_plugin') ); } @@ -87,8 +101,10 @@ public function build() { $this->subpage = $this->getContextValue('subpage'); $this->user = $this->getContextValue('user'); + $manage_mode = \Drupal::state()->get('manage_mode'); + $this->buildTabs($build); - $this->buildContent($build); + $this->buildContent($build, $manage_mode); return $build; } @@ -151,17 +167,23 @@ public function buildTabs(array &$build) { * @param array $build * Drupal renderable array being added to. */ - public function buildContent(array &$build) { + public function buildContent(array &$build, $manage_mode = FALSE) { + $build['#attached']['drupalSettings']['dragMode'] = $manage_mode; + $build['content'] = [ '#prefix' => '
', '#suffix' => '
', - '#type' => 'contact_tab_content', - '#tab' => $this->tabManager->getTabByPath($this->user, $this->subpage), - '#user' => $this->user, + '#theme' => 'contacts_dash_tab_content', '#subpage' => $this->subpage, - '#attributes' => ['class' => ['dash-content']], + '#manage_mode' => $manage_mode, + '#region_attributes' => ['class' => ['drag-area']], + '#content' => [], ]; + if ($manage_mode) { + $build['content']['#region_attributes']['class'][] = 'show'; + } + $build['messages'] = [ '#type' => 'status_messages', '#weight' => -99, diff --git a/src/Plugin/Block/ContactsEntity.php b/src/Plugin/Block/ContactsEntity.php index e4629f6..378f27c 100644 --- a/src/Plugin/Block/ContactsEntity.php +++ b/src/Plugin/Block/ContactsEntity.php @@ -5,11 +5,13 @@ use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Access\AccessResult; use Drupal\Core\Block\BlockBase; +use Drupal\Core\Entity\EntityDisplayRepository; use Drupal\Core\Entity\EntityFormBuilderInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\Form\FormBuilder; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Routing\CurrentRouteMatch; @@ -69,6 +71,13 @@ class ContactsEntity extends BlockBase implements ContainerFactoryPluginInterfac */ protected $request; + /** + * The entity display repository. + * + * @var \Drupal\Core\Entity\EntityDisplayRepository + */ + protected $entityDisplayRepository; + /** * {@inheritdoc} */ @@ -77,7 +86,8 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity_type.manager'), $container->get('entity.form_builder'), $container->get('current_route_match'), - $container->get('request_stack') + $container->get('request_stack'), + $container->get('entity_display.repository') ); } @@ -98,13 +108,16 @@ public static function create(ContainerInterface $container, array $configuratio * The current route match. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack * The request stack. + * @param \Drupal\Core\Entity\EntityDisplayRepository + * The entity display repository. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFormBuilderInterface $form_builder, CurrentRouteMatch $route_match, RequestStack $request_stack) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFormBuilderInterface $form_builder, CurrentRouteMatch $route_match, RequestStack $request_stack, EntityDisplayRepository $entity_display_repository) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityTypeManager = $entity_type_manager; $this->formBuilder = $form_builder; $this->routeMatch = $route_match; $this->request = $request_stack->getCurrentRequest(); + $this->entityDisplayRepository = $entity_display_repository; } /** @@ -157,6 +170,42 @@ public function label() { return $label; } + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $form['mode'] = [ + '#type' => 'select', + '#options' => [self::MODE_FORM => 'Form', self::MODE_VIEW => 'View'], + '#title' => $this->t('Show as'), + '#default_value' => $this->configuration['mode'], + ]; + + // @todo figure out how we can get the parent form structure. + $form['view_mode'] = [ + '#type' => 'select', + '#options' => $this->entityDisplayRepository->getViewModeOptions($this->pluginDefinition['_entity_type_id']), + '#title' => $this->t('View mode'), + '#default_value' => $this->configuration['view_mode'], + '#states' => array( + 'visible' => array( + ':input[name="settings[mode]"]' => array( + 'value' => self::MODE_VIEW, + ), + ), + ), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + $this->configuration['view_mode'] = $form_state->getValue('view_mode'); + $this->configuration['mode'] = $form_state->getValue('mode'); + } + /** * Whether we should use edit links. * diff --git a/templates/contacts-dash-manage.html.twig b/templates/contacts-dash-manage.html.twig new file mode 100644 index 0000000..32055ce --- /dev/null +++ b/templates/contacts-dash-manage.html.twig @@ -0,0 +1,5 @@ + diff --git a/templates/contacts-dash-tab-content.html.twig b/templates/contacts-dash-tab-content.html.twig new file mode 100644 index 0000000..cddd070 --- /dev/null +++ b/templates/contacts-dash-tab-content.html.twig @@ -0,0 +1 @@ +{{ content }} diff --git a/templates/contacts-dnd-card.html.twig b/templates/contacts-dnd-card.html.twig new file mode 100644 index 0000000..c20a967 --- /dev/null +++ b/templates/contacts-dnd-card.html.twig @@ -0,0 +1,59 @@ +
+
+ +
{{ label }}
+ +
+ {% if pseudo_content %} +
+
+
+
+ {% endif %} + {% if content %} +
+ {% if content.description %} +
{{ content.description }}
+ {% endif %} + {% if content.needed_hats %} +
+
+ Allowed for users with hats:
+ {%- for hat in content.needed_hats -%} + {{ hat }} + {%- endfor -%} +
+ {% endif %} + {% if content.placed_tabs %} +
+
+ Currently placed on tabs:
+ {%- for tab in content.placed_tabs -%} + {{ tab|raw }} + {%- endfor -%} +
+ {% endif %} + {% if content.configure_form %} + {{ content.configure_form }} + {% endif %} + {% if content.links %} +
+ {%- for link in content.links -%} + {{ link.text }} + {%- endfor -%} + {% endif %} +
+ {% endif %} + {% if footer %} + + {% endif %} +
diff --git a/templates/contacts-hat.html.twig b/templates/contacts-hat.html.twig new file mode 100644 index 0000000..e322bc0 --- /dev/null +++ b/templates/contacts-hat.html.twig @@ -0,0 +1,3 @@ + + {{ label }} + \ No newline at end of file