From db7a50cc88d1d14ea97ca05dc309668798765341 Mon Sep 17 00:00:00 2001 From: Andrew Berezovsky Date: Wed, 18 Apr 2018 14:35:18 +0300 Subject: [PATCH] [#155932196] Manage main menu. --- backend-gifts/composer.json | 3 + backend-gifts/composer.lock | 7 +- .../config/sync/user.role.gifts_manager.yml | 1 + .../falcon_gifts_api/falcon_gifts_api.module | 17 +++++ .../falcon_gifts_api.services.yml | 6 ++ .../MenuLinkContentEntityNormalizer.php | 41 +++++++++++ frontend-gifts/src/actions/menu.js | 21 ++++++ .../src/components/GlobalHeader/MainMenu.js | 69 ++++++++++++++----- frontend-gifts/src/reducers/index.js | 2 + frontend-gifts/src/reducers/menu.js | 41 +++++++++++ .../src/views/BasicPageView/index.js | 2 +- 11 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.services.yml create mode 100644 backend-gifts/web/modules/falcon/falcon_gifts_api/src/Normalizer/MenuLinkContentEntityNormalizer.php create mode 100644 frontend-gifts/src/actions/menu.js create mode 100644 frontend-gifts/src/reducers/menu.js diff --git a/backend-gifts/composer.json b/backend-gifts/composer.json index 42bcbbe..ed0b87c 100644 --- a/backend-gifts/composer.json +++ b/backend-gifts/composer.json @@ -95,6 +95,9 @@ ] }, "patches": { + "drupal/core": { + "Menu content is not accessible via jsonapi": "https://www.drupal.org/files/issues/2018-03-19/menu_link_content-view-permissions-2915792-16.patch" + }, "drupal/metatag": { "Fix compatibility with jsonapi": "https://www.drupal.org/files/issues/make_metatag_fields-2636852-80.patch" } diff --git a/backend-gifts/composer.lock b/backend-gifts/composer.lock index 0a862fc..8a8403e 100644 --- a/backend-gifts/composer.lock +++ b/backend-gifts/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "84a9bbf1a9161956b61b621c221556ce", + "content-hash": "5dd3f2dd5e354a6607a692f4aedffc98", "packages": [ { "name": "alchemy/zippy", @@ -2239,6 +2239,11 @@ "symfony/phpunit-bridge": "~3.2.8" }, "type": "drupal-core", + "extra": { + "patches_applied": { + "Menu content is not accessible via jsonapi": "https://www.drupal.org/files/issues/2018-03-19/menu_link_content-view-permissions-2915792-16.patch" + } + }, "autoload": { "psr-4": { "Drupal\\Core\\": "lib/Drupal/Core", diff --git a/backend-gifts/config/sync/user.role.gifts_manager.yml b/backend-gifts/config/sync/user.role.gifts_manager.yml index e535ab0..cdcc3f4 100644 --- a/backend-gifts/config/sync/user.role.gifts_manager.yml +++ b/backend-gifts/config/sync/user.role.gifts_manager.yml @@ -15,6 +15,7 @@ permissions: - 'access toolbar' - 'access user profiles' - 'add e-card item entities' + - 'administer menu' - 'administer meta tags' - 'administer nodes' - 'administer taxonomy' diff --git a/backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.module b/backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.module index 176dbe8..58d2530 100644 --- a/backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.module +++ b/backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.module @@ -5,6 +5,8 @@ * Contains falcon_gifts_api.module. */ +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Routing\RouteMatchInterface; /** @@ -22,3 +24,18 @@ function falcon_gifts_api_help($route_name, RouteMatchInterface $route_match) { default: } } + +/** + * Implements hook_entity_base_field_info(). + */ +function falcon_gifts_api_entity_base_field_info(EntityTypeInterface $entity_type) { + $fields = []; + if ($entity_type->id() == 'menu_link_content') { + $fields['url'] = BaseFieldDefinition::create('string') + ->setLabel(t('URL')) + ->setDescription(t('The URL of the menu link.')) + ->setComputed(TRUE) + ->setQueryable(FALSE); + } + return $fields; +} diff --git a/backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.services.yml b/backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.services.yml new file mode 100644 index 0000000..15e5dd1 --- /dev/null +++ b/backend-gifts/web/modules/falcon/falcon_gifts_api/falcon_gifts_api.services.yml @@ -0,0 +1,6 @@ +services: + serializer.normalizer.menu_link_content.falcon_gifts_api: + class: Drupal\falcon_gifts_api\Normalizer\MenuLinkContentEntityNormalizer + arguments: ['@jsonapi.link_manager', '@jsonapi.resource_type.repository', '@entity_type.manager'] + tags: + - { name: normalizer, priority: 22 } diff --git a/backend-gifts/web/modules/falcon/falcon_gifts_api/src/Normalizer/MenuLinkContentEntityNormalizer.php b/backend-gifts/web/modules/falcon/falcon_gifts_api/src/Normalizer/MenuLinkContentEntityNormalizer.php new file mode 100644 index 0000000..e5e8fb4 --- /dev/null +++ b/backend-gifts/web/modules/falcon/falcon_gifts_api/src/Normalizer/MenuLinkContentEntityNormalizer.php @@ -0,0 +1,41 @@ +getUrlObject(); + $url = $url_object->toString(); + if ($url_object->isRouted() && ($params = $url_object->getRouteParameters()) && isset($params['node'])) { + $node = $this->entityTypeManager->getStorage('node')->load($params['node']); + if (!$node->access('view')) { + $url = null; + } + } + + $entity->set('url', $url); + return parent::getFields($entity, $bundle, $resource_type); + } + +} diff --git a/frontend-gifts/src/actions/menu.js b/frontend-gifts/src/actions/menu.js new file mode 100644 index 0000000..6ff59aa --- /dev/null +++ b/frontend-gifts/src/actions/menu.js @@ -0,0 +1,21 @@ +import request from 'superagent'; +import jsonapify from 'superagent-jsonapify'; +import config from '../config'; + +jsonapify(request); + +export function loadAll() { + return { + type: 'GET_MENU', + payload: request + .get(`${config.backend}/v1/gifts/jsonapi/menu_link_content/menu_link_content`) + .query({ + 'fields[menu_link_content--menu_link_content]': 'uuid,title,url', + 'filter[menu_name][condition][path]': 'menu_name', + 'filter[menu_name][condition][value]': 'main', + 'filter[enabled][condition][path]': 'enabled', + 'filter[enabled][condition][value]': 1, + 'sort': 'weight' + }) + }; +} diff --git a/frontend-gifts/src/components/GlobalHeader/MainMenu.js b/frontend-gifts/src/components/GlobalHeader/MainMenu.js index 709618c..4e7f963 100644 --- a/frontend-gifts/src/components/GlobalHeader/MainMenu.js +++ b/frontend-gifts/src/components/GlobalHeader/MainMenu.js @@ -1,29 +1,66 @@ -import React from 'react'; +import React, { Component } from 'react'; import { connect } from 'react-redux'; import { NavLink } from 'react-router-dom'; +import * as menuActions from '../../actions/menu'; -const MainMenu = ({ isMenuCollapsed, onMenuClick, location }) => ( - -); +class MainMenu extends Component { + + componentWillMount() { + // Load list of pages is they haven't been loaded yet. + const { menu, loadMenu, done } = this.props; + if (!menu.list.length) { + loadMenu().then(done, done); + } + } + + render = () => { + const { isMenuCollapsed, onMenuClick, location, menu } = this.props; + + return (); + } + +} MainMenu.propTypes = { isMenuCollapsed: React.PropTypes.bool.isRequired, onMenuClick: React.PropTypes.func.isRequired, location: React.PropTypes.object.isRequired, + loadMenu: React.PropTypes.func, + done: React.PropTypes.func, + menu: React.PropTypes.shape({ + isPending: React.PropTypes.bool, + isFulfilled: React.PropTypes.bool, + isError: React.PropTypes.bool, + list: React.PropTypes.arrayOf( + React.PropTypes.shape({ + uuid: React.PropTypes.string, + title: React.PropTypes.string, + url: React.PropTypes.string + }) + ) + }) }; -// Pass location prop to NavLink explicitly. -// See https://github.com/ReactTraining/react-router/issues/4638 const mapStateToProps = state => ({ - location: state.router.location + // Pass location prop to NavLink explicitly. + // See https://github.com/ReactTraining/react-router/issues/4638 + location: state.router.location, + menu: state.menu }); -export default connect(mapStateToProps)(MainMenu); +const mapDispatchToProps = { + loadMenu: menuActions.loadAll, +}; + +export default connect(mapStateToProps, mapDispatchToProps)(MainMenu); diff --git a/frontend-gifts/src/reducers/index.js b/frontend-gifts/src/reducers/index.js index 2f4d3d6..22b0e69 100644 --- a/frontend-gifts/src/reducers/index.js +++ b/frontend-gifts/src/reducers/index.js @@ -5,6 +5,7 @@ import { giftsCorporate, giftCorporateCustomPrice } from './gifts.corporate'; import { giftsFree } from './gifts.free'; import { currentCurrency } from './currencies'; import { contactForm } from './contact'; +import { menu } from './menu'; import { messageBar } from './messageBar'; import { featuredImages } from './featuredImages'; import { pages } from './pages'; @@ -45,6 +46,7 @@ export default combineReducers({ giftsFree, giftCustomPrice, giftCorporateCustomPrice, + menu, messageBar, siteContentSettings, popup, diff --git a/frontend-gifts/src/reducers/menu.js b/frontend-gifts/src/reducers/menu.js new file mode 100644 index 0000000..b92e83d --- /dev/null +++ b/frontend-gifts/src/reducers/menu.js @@ -0,0 +1,41 @@ +export const menu = (state = { + isPending: false, + isFulfilled: false, + isError: false, + list: [] +}, action) => { + switch (action.type) { + + case 'GET_MENU_PENDING': + return { + ...state, + isPending: true, + }; + + case 'GET_MENU_FULFILLED': { + const data = action.payload.body.data; + const list = []; + + data.forEach((item) => { + list.push(item.attributes); + }); + + return { + ...state, + isPending: false, + isFulfilled: true, + list + }; + } + + case 'GET_MENU_REJECTED': + return { + ...state, + isPending: false, + isError: true, + }; + + default: + return state; + } +}; diff --git a/frontend-gifts/src/views/BasicPageView/index.js b/frontend-gifts/src/views/BasicPageView/index.js index 24ff2dc..07523d1 100644 --- a/frontend-gifts/src/views/BasicPageView/index.js +++ b/frontend-gifts/src/views/BasicPageView/index.js @@ -41,7 +41,7 @@ BasicPageView.propTypes = { isPending: React.PropTypes.bool, isFulfilled: React.PropTypes.bool, isError: React.PropTypes.bool, - pages: React.PropTypes.arrayOf( + list: React.PropTypes.arrayOf( React.PropTypes.shape({ uuid: React.PropTypes.string, title: React.PropTypes.string,