diff --git a/css/panelizer.admin.css b/css/panelizer.admin.css new file mode 100644 index 0000000..1aa3794 --- /dev/null +++ b/css/panelizer.admin.css @@ -0,0 +1,113 @@ +/** + * @file + * Styles for Panelizer wizard admin. + */ + +/* Narrow screens */ + +.panelizer-wizard-tree, +.panelizer-wizard-form { + box-sizing: border-box; +} + +/** + * Wizard actions across the top. + */ +.panelizer-wizard-actions { + text-align: right; /* LTR */ +} +.panelizer-wizard-actions ul.inline, +.panelizer-wizard-actions ul.inline li { + display: inline-block; + margin: 0; +} +.panelizer-wizard-actions ul.inline { + border-top: 1px solid black; + border-left: 1px solid black; +} +.panelizer-wizard-actions ul.inline li { + border-right: 1px solid black; + padding: .5em; +} + +/** + * The tree of wizard steps. + */ +.panelizer-wizard-tree ul { + margin: 0; + padding: 0; + list-style: none; +} +.panelizer-wizard-tree ul > li > ul { + margin-left: 1em; +} +.panelizer-wizard-tree > ul { + border: 1px solid black; + padding-bottom: .5em; + margin-bottom: 20px; +} +.panelizer-wizard-tree li { + border-bottom: 1px solid black; + padding: .5em; + padding-right: 0; +} +.panelizer-wizard-tree li:last-child { + border-bottom: 0; + padding-bottom: 0; +} + +/** + * The wizard form. + */ +.panelizer-wizard-form { + border: 1px solid black; + padding: 1em; + margin-bottom: 20px; +} + +/* Wide screens */ +@media + screen and (min-width: 780px), + (orientation: landscape) and (min-device-height: 780px) { + + /** + * Overall layout. + */ + .panelizer-wizard-tree { + float: left; /* LTR */ + width: 20%; + } + .panelizer-wizard-form { + float: left; /* LTR */ + width: 80%; + } + .panelizer-wizard-form-actions { + margin-left: 20%; /* LTR */ + } + + /** + * Make the borders look nice. + */ + .panelizer-wizard-tree > ul { + border-right: 0; /* LTR */ + } + .panelizer-wizard-form { + min-height: 400px; + } + + /** + * Right-to-left support. + */ + [dir="rtl"] .panelizer-wizard-tree, + [dir="rtl"] .panelizer-wizard-form { + float: right; + } + [dir="rtl"] .panelizer-wizard-form-actions { + margin-left: 0; + margin-right: 20%; + } + [dir="rtl"] .panelizer-wizard-tree > ul { + border-right: 1px solid black; + border-left: 0; + } +} diff --git a/panelizer.libraries.yml b/panelizer.libraries.yml index 6e9a5b2..50b9c76 100644 --- a/panelizer.libraries.yml +++ b/panelizer.libraries.yml @@ -17,4 +17,8 @@ panels_ipe: css/panels_ipe.css: {} dependencies: - panels_ipe/panels_ipe - +wizard_admin: + version: VERSION + css: + layout: + css/panelizer.admin.css: {} diff --git a/panelizer.module b/panelizer.module index 2049b16..97e28f6 100644 --- a/panelizer.module +++ b/panelizer.module @@ -18,10 +18,62 @@ function panelizer_theme() { 'panelizer_view_mode' => [ 'render element' => 'element', ], + 'panelizer_wizard_form' => [ + 'render element' => 'form', + ], + 'panelizer_wizard_tree' => [ + 'variables' => [ + 'wizard' => NULL, + 'cached_values' => [], + 'tree' => [], + 'divider' => ' ยป ', + 'step' => NULL, + ], + ], ]; } /** + * Preprocess function for panelizer-wizard-tree.html.twig. + */ +function template_preprocess_panelizer_wizard_tree(&$variables) { + /** @var $wizard \Drupal\ctools\Wizard\FormWizardInterface|\Drupal\ctools\Wizard\EntityFormWizardInterface */ + $wizard = $variables['wizard']; + $cached_values = $variables['cached_values']; + $tree = $variables['tree']; + $variables['step'] = $wizard->getStep($cached_values); + + foreach ($wizard->getOperations($cached_values) as $step => $operation) { + $parameters = $wizard->getNextParameters($cached_values); + // Override step to be the step we want. + $parameters['step'] = $step; + + // Fill in parents if there are breadcrumbs. + $parent =& $tree; + if (isset($operation['breadcrumbs'])) { + foreach ($operation['breadcrumbs'] as $breadcrumb) { + $breadcrumb_string = (string) $breadcrumb; + if (!isset($parent[$breadcrumb_string])) { + $parent[$breadcrumb_string] = [ + 'title' => $breadcrumb, + 'children' => [], + ]; + } + $parent =& $parent[$breadcrumb_string]['children']; + } + } + + $parent[$step] = [ + 'title' => !empty($operation['title']) ? $operation['title'] : '', + 'url' => new \Drupal\Core\Url($wizard->getRouteName(), $parameters), + 'step' => $step, + ]; + } + + $variables['tree'] = $tree; +} + +/** * Implements hook_entity_type_alter(). */ function panelizer_entity_type_alter(array &$entity_types) { @@ -165,7 +217,7 @@ function panelizer_form_entity_view_display_edit_form_alter(&$form, FormStateInt $form['panelizer']['enable'] = [ '#title' => t('Panelize this view mode'), '#type' => 'link', - '#url' => Url::fromRoute('panelizer.wizard', [ + '#url' => Url::fromRoute('panelizer.wizard.add', [ 'machine_name' => $display->getTargetEntityTypeId() . '__' . $display->getTargetBundle() . '__' . $display->getMode(), ]), ]; diff --git a/panelizer.routing.yml b/panelizer.routing.yml index 98bf6fa..2bda08e 100644 --- a/panelizer.routing.yml +++ b/panelizer.routing.yml @@ -14,8 +14,8 @@ panelizer.panels_ipe.revert_to_default: _custom_access: '\Drupal\panelizer\Controller\PanelizerPanelsIPEController::accessRevertToDefault' # Wizard -panelizer.wizard: - path: '/admin/panelizer/wizard/{machine_name}' +panelizer.wizard.add: + path: '/admin/structure/panelizer/add/{machine_name}' defaults: _wizard: '\Drupal\panelizer\Wizard\PanelizerWizard' _title: 'Panelizer Wizard' @@ -23,8 +23,8 @@ panelizer.wizard: requirements: _access: 'TRUE' -panelizer.wizard.step: - path: '/admin/panelizer/wizard/{machine_name}/{step}' +panelizer.wizard.add.step: + path: '/admin/structure/panelizer/add/{machine_name}/{step}' defaults: _wizard: '\Drupal\panelizer\Wizard\PanelizerWizard' _title: 'Panelizer Wizard' @@ -32,6 +32,16 @@ panelizer.wizard.step: requirements: _access: 'TRUE' +panelizer.wizard.edit: + path: '/admin/structure/panelizer/edit/{machine_name}/{step}' + defaults: + _wizard: '\Drupal\panelizer\Wizard\PanelizerEditWizard' + _title: 'Panelizer Wizard' + tempstore_id: 'panelizer.wizard' + step: 'general' + requirements: + _permission: 'administer pages' + # Contexts panelizer.wizard.step.context.add: path: '/admin/panelizer/wizard/{machine_name}/contexts/add/{context_id}' diff --git a/src/Form/PanelizerWizardContextConfigure.php b/src/Form/PanelizerWizardContextConfigure.php index 4dc2f28..ddc8c73 100644 --- a/src/Form/PanelizerWizardContextConfigure.php +++ b/src/Form/PanelizerWizardContextConfigure.php @@ -16,7 +16,7 @@ class PanelizerWizardContextConfigure extends ContextConfigure { * {@inheritdoc} */ protected function getParentRouteInfo($cached_values) { - return ['panelizer.wizard.step', [ + return ['panelizer.wizard.add.step', [ 'machine_name' => $cached_values['id'], 'step' => 'contexts', ]]; diff --git a/src/Form/PanelizerWizardContextDeleteForm.php b/src/Form/PanelizerWizardContextDeleteForm.php index 783eff9..c5339c5 100644 --- a/src/Form/PanelizerWizardContextDeleteForm.php +++ b/src/Form/PanelizerWizardContextDeleteForm.php @@ -37,7 +37,7 @@ class PanelizerWizardContextDeleteForm extends ContextDelete { public function getCancelUrl() { $cached_values = $this->getTempstore(); - return new Url('panelizer.wizard.step', [ + return new Url('panelizer.wizard.add.step', [ 'machine_name' => $cached_values['id'], 'step' => 'contexts', ]); diff --git a/src/Form/PanelizerWizardGeneralForm.php b/src/Form/PanelizerWizardGeneralForm.php index 0835246..49ebd59 100644 --- a/src/Form/PanelizerWizardGeneralForm.php +++ b/src/Form/PanelizerWizardGeneralForm.php @@ -50,7 +50,7 @@ class PanelizerWizardGeneralForm extends FormBase { $form['custom'] = [ '#type' => 'checkbox', '#title' => t('Allow custom overrides of each entity'), - '#default_value' => !empty($cached_values['one']) ? $cached_values['one'] : FALSE, + '#default_value' => !empty($cached_values['custom']) ? $cached_values['custom'] : FALSE, ]; return $form; diff --git a/src/Wizard/PanelizerEditWizard.php b/src/Wizard/PanelizerEditWizard.php new file mode 100644 index 0000000..d5a5e3e --- /dev/null +++ b/src/Wizard/PanelizerEditWizard.php @@ -0,0 +1,279 @@ +getTemporaryValue('wizard'); + // Load data in to values to be cached and managed by the + // wizard until the user clicks on Save or Cancel. + $cached_values['id'] = $this->getMachineName(); + $cached_values['access'] = []; + + if (empty($cached_values['page_variant'])) { + // Create a page variant to host the panels display + $variant_plugin_id = 'panels_variant'; + $page_variant = \Drupal::entityManager() + ->getStorage('page_variant') + ->create([ + 'variant' => $variant_plugin_id, + 'id' => $cached_values['id'] . '__variant', + ]); + $cached_values['page_variant'] = $page_variant; + + // Load the panels variant data. + $panelizer = \Drupal::service('panelizer'); + list($entity_type, $bundle, $display_id) = explode('__', $this->getMachineName()); + $variant_plugin = $panelizer->getDefaultPanelsDisplay($display_id, $entity_type, $bundle, $display_id); + $cached_values['plugin'] = $variant_plugin; + $cached_values['contexts'] = $cached_values['plugin']->getContexts(); + + // Load general settings. + $settings = $panelizer->getPanelizerSettings($entity_type, $bundle, $display_id); + $cached_values['custom'] = $settings['custom']; + } + $form_state->setTemporaryValue('wizard', $cached_values); + $form = parent::buildForm($form, $form_state); + return $form; + } + + /** + * {@inheritdoc} + */ + public function getWizardLabel() { + return $this->t('Wizard Information'); + } + + /** + * {@inheritdoc} + */ + public function getMachineLabel() { + return $this->t('Wizard Test Name'); + } + + /** + * {@inheritdoc} + */ + public function getOperations($cached_values) { + $operations = [ + 'general' => [ + 'form' => PanelizerWizardGeneralForm::class, + 'title' => $this->t('General settings'), + ], + 'contexts' => [ + 'form' => PanelizerWizardContextForm::class, + 'title' => $this->t('Contexts'), + ], + ]; + + // Add any wizard operations from the plugin itself. + if (isset($cached_values['page_variant'])) { + $page_variant = $cached_values['page_variant']; + $variant_plugin = $page_variant->getVariantPlugin(); + $variant_plugin->setContexts($this->getContexts($cached_values)); + foreach ($variant_plugin->getWizardOperations($cached_values) as $name => $operation) { + $operation['values']['plugin'] = $variant_plugin; + $operation['submit'][] = '::submitVariantStep'; + $operations[$name] = $operation; + } + + // Change the class that manages the Content step. + if (isset($operations['content'])) { + $operations['content']['form'] = PanelizerWizardContentForm::class; + } + } + + return $operations; + } + + /** + * {@inheritdoc} + */ + public function getRouteName() { + return 'panelizer.wizard.edit'; + } + + /** + * {@inheritdoc} + */ + protected function customizeForm(array $form, FormStateInterface $form_state) { + $cached_values = $form_state->getTemporaryValue('wizard'); + + // The page actions. + $form['wizard_actions'] = [ + '#theme' => 'links', + '#links' => [], + '#attributes' => [ + 'class' => ['inline'], + ] + ]; + + // The tree of wizard steps. + $form['wizard_tree'] = [ + '#theme' => ['panelizer_wizard_tree'], + '#wizard' => $this, + '#cached_values' => $form_state->getTemporaryValue('wizard'), + ]; + + $form['#theme'] = 'panelizer_wizard_form'; + $form['#attached']['library'][] = 'panelizer/wizard_admin'; + return $form; + } + + /** + * {@inheritdoc} + */ + protected function actions(FormInterface $form_object, FormStateInterface $form_state) { + $cached_values = $form_state->getTemporaryValue('wizard'); + $operation = $this->getOperation($cached_values); + + $actions = []; + + $actions['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Update'), + '#validate' => [ + '::populateCachedValues', + [$form_object, 'validateForm'], + ], + '#submit' => [ + [$form_object, 'submitForm'], + ], + ]; + + $actions['update_and_save'] = [ + '#type' => 'submit', + '#value' => $this->t('Update and save'), + '#button_type' => 'primary', + '#validate' => [ + '::populateCachedValues', + [$form_object, 'validateForm'], + ], + '#submit' => [ + [$form_object, 'submitForm'], + ], + ]; + + $actions['finish'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + '#validate' => [ + '::populateCachedValues', + [$form_object, 'validateForm'], + ], + '#submit' => [ + [$form_object, 'submitForm'], + ], + ]; + + $actions['cancel'] = [ + '#type' => 'submit', + '#value' => $this->t('Cancel'), + '#submit' => [ + '::clearTempstore' + ], + ]; + + // Add any submit or validate functions for the step and the global ones. + foreach (['submit', 'update_and_save', 'finish'] as $button) { + if (isset($operation['validate'])) { + $actions[$button]['#validate'] = array_merge($actions[$button]['#validate'], $operation['validate']); + } + $actions[$button]['#validate'][] = '::validateForm'; + if (isset($operation['submit'])) { + $actions[$button]['#submit'] = array_merge($actions[$button]['#submit'], $operation['submit']); + } + $actions[$button]['#submit'][] = '::submitForm'; + } + $actions['update_and_save']['#submit'][] = '::finish'; + $actions['finish']['#submit'][] = '::finish'; + + if ($form_state->get('ajax')) { + $cached_values = $form_state->getTemporaryValue('wizard'); + $ajax_parameters = $this->getNextParameters($cached_values); + $ajax_parameters['step'] = $this->getStep($cached_values); + $actions['submit']['#ajax'] = [ + 'callback' => '::ajaxSubmit', + 'url' => Url::fromRoute($this->getRouteName(), $ajax_parameters), + 'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], + ]; + $actions['update_and_save']['#ajax'] = [ + 'callback' => '::ajaxFinish', + 'url' => Url::fromRoute($this->getRouteName(), $ajax_parameters), + 'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], + ]; + $actions['finish']['#ajax'] = [ + 'callback' => '::ajaxFinish', + 'url' => Url::fromRoute($this->getRouteName(), $ajax_parameters), + 'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], + ]; + } + + return $actions; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $cached_values = $form_state->getTemporaryValue('wizard'); + // Normally, the wizard only saves the data when the 'Next' button is + // clicked, but we want to save the data always when editing. + $this->getTempstore()->set($this->getMachineName(), $cached_values); + } + + /** + * Clears the temporary store. + * + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + */ + public function clearTempstore(array &$form, FormStateInterface $form_state) { + $this->getTempstore()->delete($this->getMachineName()); + } + + /** + * Wraps the context mapper. + * + * @return \Drupal\page_manager\ContextMapperInterface + */ + protected function getContextMapper() { + return \Drupal::service('page_manager.context_mapper'); + } + + /** + * {@inheritdoc} + */ + protected function getContexts($cached_values) { + if (!isset($cached_values['contexts'])) { + $cached_values['contexts'] = []; + } + + return $this->getContextMapper()->getContextValues($cached_values['contexts']); + } + +} diff --git a/src/Wizard/PanelizerWizard.php b/src/Wizard/PanelizerWizard.php index 0c6cd54..772024a 100644 --- a/src/Wizard/PanelizerWizard.php +++ b/src/Wizard/PanelizerWizard.php @@ -7,10 +7,11 @@ namespace Drupal\panelizer\Wizard; - use Drupal\Core\Form\FormStateInterface; use Drupal\ctools\Wizard\FormWizardBase; use Drupal\panelizer\Form\PanelizerWizardContentForm; +use Drupal\panelizer\Form\PanelizerWizardContextForm; +use Drupal\panelizer\Form\PanelizerWizardGeneralForm; class PanelizerWizard extends FormWizardBase { @@ -34,11 +35,11 @@ class PanelizerWizard extends FormWizardBase { public function getOperations($cached_values) { $operations = [ 'general' => [ - 'form' => 'Drupal\panelizer\Form\PanelizerWizardGeneralForm', + 'form' => PanelizerWizardGeneralForm::class, 'title' => $this->t('General settings'), ], 'contexts' => [ - 'form' => 'Drupal\panelizer\Form\PanelizerWizardContextForm', + 'form' => PanelizerWizardContextForm::class, 'title' => $this->t('Contexts'), ], ]; @@ -67,7 +68,7 @@ class PanelizerWizard extends FormWizardBase { * {@inheritdoc} */ public function getRouteName() { - return 'panelizer.wizard.step'; + return 'panelizer.wizard.add.step'; } /** @@ -81,14 +82,13 @@ class PanelizerWizard extends FormWizardBase { $cached_values['access'] = []; if (empty($cached_values['page_variant'])) { $variant_plugin_id = 'panels_variant'; - $panels_variant = \Drupal::entityManager() + $page_variant = \Drupal::entityManager() ->getStorage('page_variant') ->create([ 'variant' => $variant_plugin_id, - //'page' => $page->id(), 'id' => $cached_values['id'] . '__variant', ]); - $cached_values['page_variant'] = $panels_variant; + $cached_values['page_variant'] = $page_variant; } $form_state->setTemporaryValue('wizard', $cached_values); return $form; @@ -108,6 +108,7 @@ class PanelizerWizard extends FormWizardBase { $display = $panelizer->getEntityViewDisplay($entity_type, $bundle, $display_id); $settings = $panelizer->getPanelizerSettings($entity_type, $bundle, $display_id); $settings['enable'] = TRUE; + $settings['custom'] = $cached_values['custom']; /** @var \Drupal\panelizer\Plugin\PanelizerEntityInterface $panelizer_entity_plugin */ $panelizer_entity_plugin = $panelizer_entity_manager->createInstance($display->getTargetEntityTypeId(), []); $displays = $display->getThirdPartySetting('panelizer', 'displays', []); @@ -116,6 +117,7 @@ class PanelizerWizard extends FormWizardBase { $panelizer->setPanelizerSettings($entity_type, $bundle, $display_id, $settings, $display); parent::finish($form, $form_state); + $form_state->setRedirect('panelizer.wizard.edit', ['machine_name' => $cached_values['id']]); } /** diff --git a/templates/panelizer-wizard-form.html.twig b/templates/panelizer-wizard-form.html.twig new file mode 100644 index 0000000..a691162 --- /dev/null +++ b/templates/panelizer-wizard-form.html.twig @@ -0,0 +1,31 @@ +{# +/** + * @file + * Default theme implementation for a 'form' element. + * + * Available variables + * - attributes: A list of HTML attributes for the wrapper element. + * - children: The child elements of the form. + * + * @see template_preprocess_form() + * + * @ingroup themeable + */ +#} +
+
+ {{ form.wizard_actions }} +
+
+
+ {{ form.wizard_tree }} +
+
+ {{ form|without('wizard_actions', 'wizard_tree', 'actions') }} +
+
+ +
+ {{ form.actions }} +
+
diff --git a/templates/panelizer-wizard-tree.html.twig b/templates/panelizer-wizard-tree.html.twig new file mode 100644 index 0000000..7d48fcf --- /dev/null +++ b/templates/panelizer-wizard-tree.html.twig @@ -0,0 +1,47 @@ +{# +/** + * @file + * Default theme implementation to display wizard tree. + * + * Available variables: + * - step: The current step name. + * - tree: A nested list of menu items. Each menu item contains: + * - title: The menu link title. + * - url: The menu link url, instance of \Drupal\Core\Url + * - children: The menu item child items. + * - step: The name of the step. + * + * @ingroup themeable + */ +#} +{% import _self as panelizer %} + +{# + We call a macro which calls itself to render the full tree. + @see http://twig.sensiolabs.org/doc/tags/macro.html +#} +{{ panelizer.wizard_tree(tree, step, 0) }} + +{% macro wizard_tree(items, step, menu_level) %} + {% import _self as panelizer %} + {% if items %} + + {% endif %} +{% endmacro %}