diff --git a/core/modules/workspace/css/workspace.overview.css b/core/modules/workspace/css/workspace.overview.css new file mode 100644 index 0000000000000000000000000000000000000000..2e8e812ba273529f87abf81ba286c1dd253ade5b --- /dev/null +++ b/core/modules/workspace/css/workspace.overview.css @@ -0,0 +1,9 @@ +/** + * @file + * Styling for the Workspace overview table. + */ + +/** @todo Move to Seven theme before Workspace is marked stable. */ +tr.active-workspace { + background-color: #ebeae4; +} diff --git a/core/modules/workspace/css/workspace.toolbar.css b/core/modules/workspace/css/workspace.toolbar.css index 9c69720fe626ebc947617d8b8feffe761a6723cd..4194a66e120fcb4d70a558d879b3f42ec5f53010 100644 --- a/core/modules/workspace/css/workspace.toolbar.css +++ b/core/modules/workspace/css/workspace.toolbar.css @@ -3,6 +3,18 @@ * Styling for Workspace module's toolbar tab. */ +/** + * @todo Remove this after https://www.drupal.org/project/drupal/issues/2986056 + * has been solved. + */ +.workspace-dialog #drupal-off-canvas * { + background: initial; +} +.workspace-dialog #drupal-off-canvas { + background: #444; +} + + /* Tab appearance. */ .toolbar .toolbar-bar .workspace-toolbar-tab { float: right; /* LTR */ @@ -12,43 +24,244 @@ float: left; } .toolbar .toolbar-bar .workspace-toolbar-tab--is-default { - background-color: #77b259; + background-color: #81C071; } .toolbar .toolbar-bar .workspace-toolbar-tab .toolbar-item { + color: #000000; margin: 0; } .toolbar .toolbar-icon-workspace:before { - background-image: url("../icons/ffffff/workspace.svg"); + background-image: url("../icons/000000/workspace.svg"); } -/* Manage workspaces link */ -.toolbar .toolbar-tray-vertical .manage-workspaces { - text-align: right; /* LTR */ - padding: 1em; +/* Off canvas dialog */ +.workspace-dialog.ui-dialog-off-canvas a:focus { + outline:none; } -[dir="rtl"] .toolbar .toolbar-tray-vertical .manage-workspaces { - text-align: left; + +.workspace-dialog.ui-dialog-off-canvas #drupal-off-canvas, +.workspace-dialog.ui-dialog-off-canvas { + background: #333333; + padding: 0; } -.toolbar .toolbar-tray-horizontal .manage-workspaces { - float: right; /* LTR */ + +.workspace-dialog.ui-widget.ui-widget-content { + height: 100% !important; } -[dir="rtl"] .toolbar .toolbar-tray-horizontal .manage-workspaces { - float: left; + +.workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar { + visibility: hidden; + position: relative; +} + +.workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar .ui-button { + visibility: visible; + z-index: 101; +} + +#drupal-off-canvas .active-workspace { + background-color: #444444; + width: 100%; + padding: 20px 40px 0 20px; + height: 140px; + position: relative; + top: 16px; +} + +@media all and (min-width: 767px) { + #drupal-off-canvas .active-workspace { + padding: 20px 40px 0 40px; + } +} + +#drupal-off-canvas .active-workspace__title { + font-size: 0.8125rem; + font-weight: bold; +} + +#drupal-off-canvas .active-workspace__label { + color: #ffffff; + font-size: 1.285em; + margin-top: 0.5em; + margin-left: 3.2rem; +} + +#drupal-off-canvas .active-workspace__manage { + font-size: 0.9286em; + margin-left: 3.2rem; + white-space: nowrap; + outline-color: currentColor; +} + +#drupal-off-canvas .active-workspace__actions { + position: relative; + top: 1em; +} + +#drupal-off-canvas .active-workspace__button { + border-radius: 20px; + background-image: linear-gradient(to bottom, #007bc6, #0071b8); + border: solid 1px #1e5c90; + padding: 5px 22px; + color: #ffffff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5); + font-weight: bold; +} + +#drupal-off-canvas .active-workspace__button:hover { + text-decoration: none; +} + +#drupal-off-canvas .all-workspaces { + position: fixed; + bottom: 1em; + left: 20px; + color: #ffffff; + outline-color: currentColor; +} + +#drupal-off-canvas .workspaces ul { + display: block; +} + +#drupal-off-canvas .workspaces li { + flex: 1; + margin-bottom: 1px; +} + +#drupal-off-canvas .workspaces a { + background-color: #555555; + box-sizing: border-box; + padding: 20px 0 0 50px; + margin-right: 1px; + color: #FFFFFF; + font-size: 0.929em; + font-weight: bold; + text-decoration: none; + position: relative; + display: block; + height: 73px; +} + +#drupal-off-canvas .active-workspace__label:before, +#drupal-off-canvas .workspaces__item:before { + background: url("../icons/f0a100/ws_icon.svg") center center no-repeat; + background-size: 100% auto; + content: ''; + display: block; + height: 20px; + width: 20px; + left: 20px; + position: absolute; +} + +#drupal-off-canvas .active-workspace--default .active-workspace__label:before, +#drupal-off-canvas .workspaces__item--default:before { + background-image: url("../icons/81c071/ws_icon.svg"); +} + +#drupal-off-canvas .active-workspace__label:before { + height: 40px; + width: 40px; + left: 20px; +} + + +@media all and (min-width: 767px) { + #drupal-off-canvas .active-workspace__label:before { + left: 40px; + } } -/* Individual workspace links */ -.toolbar-horizontal .toolbar-tray .toolbar-menu li + li { - border-left: 1px solid #ddd; /* LTR */ + .workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar { + padding: 0; + top: 39px; + } + +.workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close { + right: 0.5em; + top: 1em; } -[dir="rtl"] .toolbar-horizontal .toolbar-tray .toolbar-menu li + li { - border-left: 0 none; - border-right: 1px solid #ddd; + +@media all and (max-width: 766px) { + .toolbar .toolbar-icon-workspace { + padding-left: 2.75em; + padding-right: 1.3333em; + text-indent: 0; + width: 8em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .toolbar .toolbar-icon-workspace:before { + background-size: 100% auto; + left: 0.6667em; + width: 20px; + } } -.toolbar-horizontal .toolbar-tray .toolbar-menu li:last-child { - border-right: 1px solid #ddd; + +@media all and (min-width: 767px) { + #drupal-off-canvas .active-workspace { + right: 0; + top: 0; + position: fixed; + width: calc(30% - 80px); + padding: 20px 40px 0; + height: 140px; + } + + #drupal-off-canvas .all-workspaces { + padding-left: 20px; + position: relative; + margin-top: 31px; + left: 0; + top: 27px; + } + + .workspace-dialog.ui-widget.ui-widget-content { + height: 161px !important; + } + + #drupal-off-canvas .workspaces { + width: 70%; + bottom: 0; + position: absolute; + } + + #drupal-off-canvas .workspaces ul { + display: flex; + flex-direction: row; + } + + #drupal-off-canvas .workspaces li { + margin-bottom: 0; + } + + #drupal-off-canvas .active-workspace__actions { + position: absolute; + bottom: 1em; + top: unset; + } + + .workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar { + top: 0; + } + + .workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close, + .workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close:hover, + .workspace-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close:focus { + top: 1.5em; + } + } -[dir="rtl"] .toolbar-horizontal .toolbar-tray .toolbar-menu li:last-child { - border-left: 1px solid #ddd; + +/* Make dialog width 100% for workspace mobile viewports. */ +@media all and (max-width: 48em) { + .ui-dialog.workspace-dialog { + min-width: 100%; + max-width: 100%; + } } diff --git a/core/modules/workspace/icons/000000/workspace.svg b/core/modules/workspace/icons/000000/workspace.svg new file mode 100644 index 0000000000000000000000000000000000000000..5bf38bc97576949c3afb73373e50c38392c772e6 --- /dev/null +++ b/core/modules/workspace/icons/000000/workspace.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/modules/workspace/icons/81c071/ws_icon.svg b/core/modules/workspace/icons/81c071/ws_icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..38566fba3dfe04c9c6d34b5211c7cdc904644a10 --- /dev/null +++ b/core/modules/workspace/icons/81c071/ws_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/modules/workspace/icons/f0a100/ws_icon.svg b/core/modules/workspace/icons/f0a100/ws_icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..0e40a504803c87d0d0d008fa2776d899b4002af1 --- /dev/null +++ b/core/modules/workspace/icons/f0a100/ws_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/modules/workspace/icons/ffffff/workspace.svg b/core/modules/workspace/icons/ffffff/workspace.svg deleted file mode 100644 index 299ff26cb5fefcb5b4d699b427b8526b77afa88b..0000000000000000000000000000000000000000 --- a/core/modules/workspace/icons/ffffff/workspace.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/core/modules/workspace/src/Form/WorkspaceActivateForm.php b/core/modules/workspace/src/Form/WorkspaceActivateForm.php index 5fd9488289938a1c772ff704ab2534da1af2e422..0058f59eb17de9f9d43957b262b58caf0bf21dd7 100644 --- a/core/modules/workspace/src/Form/WorkspaceActivateForm.php +++ b/core/modules/workspace/src/Form/WorkspaceActivateForm.php @@ -107,7 +107,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { try { $this->workspaceManager->setActiveWorkspace($this->entity); $this->messenger->addMessage($this->t('%workspace_label is now the active workspace.', ['%workspace_label' => $this->entity->label()])); - $form_state->setRedirectUrl($this->entity->toUrl('collection')); + $form_state->setRedirect(''); } catch (WorkspaceAccessException $e) { $this->messenger->addError($this->t('You do not have access to activate the %workspace_label workspace.', ['%workspace_label' => $this->entity->label()])); diff --git a/core/modules/workspace/src/Form/WorkspaceSwitcherForm.php b/core/modules/workspace/src/Form/WorkspaceSwitcherForm.php index d726ad02c41e33104a73b75162249f0b35590687..15ec2ecbf8589fdafe4abb54e9af4d2e3e8115a7 100644 --- a/core/modules/workspace/src/Form/WorkspaceSwitcherForm.php +++ b/core/modules/workspace/src/Form/WorkspaceSwitcherForm.php @@ -122,7 +122,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) { try { $this->workspaceManager->setActiveWorkspace($workspace); $this->messenger->addMessage($this->t('%workspace_label is now the active workspace.', ['%workspace_label' => $workspace->label()])); - $form_state->setRedirect(''); } catch (WorkspaceAccessException $e) { $this->messenger->addError($this->t('You do not have access to activate the %workspace_label workspace.', ['%workspace_label' => $workspace->label()])); diff --git a/core/modules/workspace/src/WorkspaceListBuilder.php b/core/modules/workspace/src/WorkspaceListBuilder.php index f6bb9340ec228129436256f5e7bfcb344561ff73..c2140bde7be030e2cd0e40a8c2ba022699a893f9 100644 --- a/core/modules/workspace/src/WorkspaceListBuilder.php +++ b/core/modules/workspace/src/WorkspaceListBuilder.php @@ -2,10 +2,13 @@ namespace Drupal\workspace; +use Drupal\Component\Serialization\Json; +use Drupal\Core\Ajax\AjaxHelperTrait; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityListBuilder; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -15,6 +18,8 @@ */ class WorkspaceListBuilder extends EntityListBuilder { + use AjaxHelperTrait; + /** * The workspace manager service. * @@ -54,7 +59,6 @@ public static function createInstance(ContainerInterface $container, EntityTypeI public function buildHeader() { $header['label'] = $this->t('Workspace'); $header['uid'] = $this->t('Owner'); - $header['status'] = $this->t('Status'); return $header + parent::buildHeader(); } @@ -64,12 +68,17 @@ public function buildHeader() { */ public function buildRow(EntityInterface $entity) { /** @var \Drupal\workspace\WorkspaceInterface $entity */ - $row['label'] = $this->t('@label (@id)', ['@label' => $entity->label(), '@id' => $entity->id()]); - $row['owner'] = $entity->getOwner()->getDisplayname(); - $active_workspace = $this->workspaceManager->getActiveWorkspace()->id(); - $row['status'] = $active_workspace == $entity->id() ? $this->t('Active') : $this->t('Inactive'); + $row['data'] = [ + 'label' => $entity->label(), + 'owner' => $entity->getOwner()->getDisplayname(), + ]; + $row['data'] = $row['data'] + parent::buildRow($entity); - return $row + parent::buildRow($entity); + $active_workspace = $this->workspaceManager->getActiveWorkspace(); + if ($entity->id() === $active_workspace->id()) { + $row['class'] = 'active-workspace'; + } + return $row; } /** @@ -85,7 +94,7 @@ public function getDefaultOperations(EntityInterface $entity) { $active_workspace = $this->workspaceManager->getActiveWorkspace(); if ($entity->id() != $active_workspace->id()) { $operations['activate'] = [ - 'title' => $this->t('Set Active'), + 'title' => $this->t('Switch to @workspace', ['@workspace' => $entity->label()]), // Use a weight lower than the one of the 'Edit' operation because we // want the 'Activate' operation to be the primary operation. 'weight' => 0, @@ -106,4 +115,126 @@ public function getDefaultOperations(EntityInterface $entity) { return $operations; } + /** + * {@inheritdoc} + */ + public function load() { + $entities = parent::load(); + // Make the active workspace more visible by moving it first in the list. + $active_workspace = $this->workspaceManager->getActiveWorkspace(); + $entities = [$active_workspace->id() => $entities[$active_workspace->id()]] + $entities; + return $entities; + } + + /** + * {@inheritdoc} + */ + public function render() { + $build = parent::render(); + if ($this->isAjax()) { + $this->offCanvasRender($build); + } + else { + $build['#attached'] = [ + 'library' => ['workspace/drupal.workspace.overview'], + ]; + } + return $build; + } + + /** + * Renders the off canvas elements. + * + * @param array $build + * A render array. + */ + protected function offCanvasRender(array &$build) { + $active_workspace = $this->workspaceManager->getActiveWorkspace(); + $row_count = count($build['table']['#rows']); + $build['active_workspace'] = [ + '#type' => 'container', + '#weight' => -20, + '#attributes' => [ + 'class' => [ + 'active-workspace', + $active_workspace->isDefaultWorkspace() ? 'active-workspace--default' : 'active-workspace--not-default', + 'active-workspace--' . $active_workspace->id(), + ], + ], + 'label' => [ + '#type' => 'label', + '#prefix' => '
' . $this->t('Current workspace:') . '
', + '#title' => $active_workspace->label(), + '#title_display' => '', + '#attributes' => ['class' => 'active-workspace__label'], + ], + 'manage' => [ + '#type' => 'link', + '#title' => $this->t('Manage workspaces'), + '#url' => $active_workspace->toUrl('collection'), + '#attributes' => [ + 'class' => ['active-workspace__manage'], + ], + ], + ]; + if (!$active_workspace->isDefaultWorkspace()) { + $build['active_workspace']['actions'] = [ + '#type' => 'container', + '#weight' => 20, + '#attributes' => [ + 'class' => ['active-workspace__actions'], + ], + 'deploy' => [ + '#type' => 'link', + '#title' => $this->t('Deploy content'), + '#url' => $active_workspace->toUrl('deploy-form', ['query' => ['destination' => $active_workspace->toUrl('collection')->toString()]]), + '#attributes' => [ + 'class' => ['button', 'active-workspace__button'], + ], + ], + ]; + } + if ($row_count > 2) { + $build['all_workspaces'] = [ + '#type' => 'link', + '#title' => $this->t('View all @count workspaces', ['@count' => $row_count]), + '#url' => $active_workspace->toUrl('collection'), + '#attributes' => [ + 'class' => ['all-workspaces'], + ], + ]; + } + $items = []; + $rows = array_slice($build['table']['#rows'], 0, 5, TRUE); + foreach ($rows as $id => $row) { + if ($active_workspace->id() !== $id) { + $url = Url::fromRoute('entity.workspace.activate_form', ['workspace' => $id]); + $default_class = $id === WorkspaceInterface::DEFAULT_WORKSPACE ? 'workspaces__item--default' : 'workspaces__item--not-default'; + $items[] = [ + '#type' => 'link', + '#title' => $row['data']['label'], + '#url' => $url, + '#attributes' => [ + 'class' => ['use-ajax', 'workspaces__item', $default_class], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 500, + ]), + ], + ]; + } + } + $build['workspaces'] = [ + '#theme' => 'item_list', + '#items' => $items, + '#wrapper_attributes' => ['class' => ['workspaces']], + '#cache' => [ + 'contexts' => $this->entityType->getListCacheContexts(), + 'tags' => $this->entityType->getListCacheTags(), + ], + ]; + unset($build['table']); + unset($build['pager']); + } + } diff --git a/core/modules/workspace/workspace.libraries.yml b/core/modules/workspace/workspace.libraries.yml index c7aad42bb36f3caf41da3f7c964913b9a8778967..a480f90ef7e4e0529fa08e9afeab19e1b04dc177 100644 --- a/core/modules/workspace/workspace.libraries.yml +++ b/core/modules/workspace/workspace.libraries.yml @@ -3,3 +3,8 @@ drupal.workspace.toolbar: css: theme: css/workspace.toolbar.css: {} +drupal.workspace.overview: + version: VERSION + css: + theme: + css/workspace.overview.css: {} diff --git a/core/modules/workspace/workspace.module b/core/modules/workspace/workspace.module index ced2be46b4467e0c97637b23bf677404028a0dec..4be4141e4bab1ba068f24e9faee3927eefbb5007 100644 --- a/core/modules/workspace/workspace.module +++ b/core/modules/workspace/workspace.module @@ -6,8 +6,6 @@ */ use Drupal\Component\Serialization\Json; -use Drupal\Core\Cache\Cache; -use Drupal\Core\Url; use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; @@ -164,16 +162,6 @@ function workspace_toolbar() { /** @var \Drupal\workspace\WorkspaceInterface $active_workspace */ $active_workspace = \Drupal::service('workspace.manager')->getActiveWorkspace(); - $configure_link = NULL; - if ($current_user->hasPermission('administer workspaces')) { - $configure_link = [ - '#type' => 'link', - '#title' => t('Manage workspaces'), - '#url' => $active_workspace->toUrl('collection'), - '#options' => ['attributes' => ['class' => ['manage-workspaces']]], - ]; - } - $items['workspace'] = [ '#type' => 'toolbar_item', 'tab' => [ @@ -182,14 +170,17 @@ function workspace_toolbar() { '#url' => $active_workspace->toUrl('collection'), '#attributes' => [ 'title' => t('Switch workspace'), - 'class' => ['toolbar-icon', 'toolbar-icon-workspace'], + 'class' => ['use-ajax', 'toolbar-icon', 'toolbar-icon-workspace'], + 'data-dialog-type' => 'dialog', + 'data-dialog-renderer' => 'off_canvas_top', + 'data-dialog-options' => Json::encode([ + 'height' => 161, + 'classes' => [ + 'ui-dialog' => 'workspace-dialog', + ], + ]), ], ], - 'tray' => [ - '#heading' => t('Workspaces'), - 'workspaces' => workspace_build_renderable_links(), - 'configure' => $configure_link, - ], '#wrapper_attributes' => [ 'class' => ['workspace-toolbar-tab'], ], @@ -207,61 +198,3 @@ function workspace_toolbar() { return $items; } - -/** - * Returns an array of workspace activation form links, suitable for rendering. - * - * @return array - * A render array containing links to the workspace activation form. - */ -function workspace_build_renderable_links() { - $entity_type_manager = \Drupal::entityTypeManager(); - /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository */ - $entity_repository = \Drupal::service('entity.repository'); - /** @var \Drupal\workspace\WorkspaceInterface $active_workspace */ - $active_workspace = \Drupal::service('workspace.manager')->getActiveWorkspace(); - - $links = $cache_tags = []; - foreach ($entity_type_manager->getStorage('workspace')->loadMultiple() as $workspace) { - $workspace = $entity_repository->getTranslationFromContext($workspace); - - // Add the 'is-active' class for the currently active workspace. - $options = []; - if ($workspace->id() === $active_workspace->id()) { - $options['attributes']['class'][] = 'is-active'; - } - - // Get the URL of the workspace activation form and display it in a modal. - $url = Url::fromRoute('entity.workspace.activate_form', ['workspace' => $workspace->id()], $options); - if ($url->access()) { - $links[$workspace->id()] = [ - 'type' => 'link', - 'title' => $workspace->label(), - 'url' => $url, - 'attributes' => [ - 'class' => ['use-ajax'], - 'data-dialog-type' => 'modal', - 'data-dialog-options' => Json::encode([ - 'width' => 500, - ]), - ], - ]; - $cache_tags = Cache::mergeTags($cache_tags, $workspace->getCacheTags()); - } - } - - if (!empty($links)) { - $links = [ - '#theme' => 'links__toolbar_workspaces', - '#links' => $links, - '#attributes' => [ - 'class' => ['toolbar-menu'], - ], - '#cache' => [ - 'tags' => $cache_tags, - ], - ]; - } - - return $links; -}