diff --git a/package.json b/package.json index bdc02403762ca..10660d17e3408 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ }, "dependencies": { "@elastic/datemath": "5.0.2", - "@elastic/eui": "9.2.1", + "@elastic/eui": "9.4.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.2", diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js b/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js index d650e29cd6629..0cc5ad3cc5363 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js @@ -82,7 +82,7 @@ It allows you to monitor the performance of thousands of applications in real ti '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html', }, }), - euiIconType: 'apmApp', + euiIconType: 'logoAPM', artifacts: artifacts, onPrem: onPremInstructions(config), elasticCloud: createElasticCloudInstructions(config), diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js b/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js index b47405afc38f9..7608a93a48782 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js @@ -43,7 +43,7 @@ export function natsLogsSpecProvider(server, context) { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-nats.html', }, }), - euiIconType: 'logoNats', + // euiIconType: 'logoNats', artifacts: { dashboards: [ { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js index cba7cd9477602..86f2497e303ff 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js @@ -39,7 +39,7 @@ export function natsMetricsSpecProvider(server, context) { learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-nats.html', }, }), - euiIconType: 'logoNats', + // euiIconType: 'logoNats', artifacts: { dashboards: [ { diff --git a/src/legacy/ui/public/chrome/api/nav.d.ts b/src/legacy/ui/public/chrome/api/nav.d.ts index 6d5154c824cd3..f7c639bc5733c 100644 --- a/src/legacy/ui/public/chrome/api/nav.d.ts +++ b/src/legacy/ui/public/chrome/api/nav.d.ts @@ -28,6 +28,7 @@ export interface NavLink { subUrlBase: string; id: string; euiIconType: IconType; + icon?: string; active: boolean; lastSubUrl?: string; hidden?: boolean; diff --git a/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx b/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx index 25d20814a477b..be0028bf2b2d3 100644 --- a/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx +++ b/src/legacy/ui/public/chrome/directives/header_global_nav/components/header.tsx @@ -19,8 +19,7 @@ import Url from 'url'; -import classNames from 'classnames'; -import React, { Component, Fragment } from 'react'; +import React, { Component, createRef, Fragment } from 'react'; import * as Rx from 'rxjs'; import { @@ -39,16 +38,14 @@ import { EuiHideFor, EuiHorizontalRule, EuiIcon, - EuiListGroup, + // @ts-ignore + EuiImage, // @ts-ignore EuiListGroupItem, // @ts-ignore EuiNavDrawer, // @ts-ignore - EuiNavDrawerFlyout, - // @ts-ignore - EuiNavDrawerMenu, - EuiOutsideClickDetector, + EuiNavDrawerGroup, // @ts-ignore EuiShowFor, } from '@elastic/eui'; @@ -79,6 +76,11 @@ interface Props { intl: InjectedIntl; } +// Providing a buffer between the limit and the cut off index +// protects from truncating just the last couple (6) characters +const TRUNCATE_LIMIT: number = 64; +const TRUNCATE_AT: number = 58; + function extendRecentlyAccessedHistoryItem( navLinks: NavLink[], recentlyAccessed: RecentlyAccessedHistoryItem @@ -115,16 +117,15 @@ function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { } } +function truncateRecentItemLabel(label: string): string { + if (label.length > TRUNCATE_LIMIT) { + label = `${label.substring(0, TRUNCATE_AT)}…`; + } + + return label; +} + interface State { - isCollapsed: boolean; - flyoutIsCollapsed: boolean; - flyoutIsAnimating: boolean; - navFlyoutTitle: string; - navFlyoutContent: []; - mobileIsHidden: boolean; - showScrollbar: boolean; - outsideClickDisabled: boolean; - isManagingFocus: boolean; navLinks: Array>; recentlyAccessed: Array>; forceNavigation: boolean; @@ -132,23 +133,12 @@ interface State { class HeaderUI extends Component { private subscription?: Rx.Subscription; - private timeoutID?: ReturnType; - private timeoutExpand?: ReturnType; - private timeoutScrollbar?: ReturnType; + private navDrawerRef = createRef(); constructor(props: Props) { super(props); this.state = { - isCollapsed: true, - flyoutIsCollapsed: true, - flyoutIsAnimating: false, - navFlyoutTitle: '', - navFlyoutContent: [], - mobileIsHidden: true, - showScrollbar: false, - outsideClickDisabled: true, - isManagingFocus: false, navLinks: [], recentlyAccessed: [], forceNavigation: false, @@ -197,14 +187,17 @@ class HeaderUI extends Component { public renderMenuTrigger() { return ( - + this.navDrawerRef.current.toggleOpen()} + > ); } public render() { - const { appTitle, breadcrumbs$, isVisible, navControls, helpExtension$ } = this.props; + const { appTitle, breadcrumbs$, isVisible, navControls, helpExtension$, intl } = this.props; const { navLinks, recentlyAccessed } = this.state; if (!isVisible) { @@ -214,6 +207,52 @@ class HeaderUI extends Component { const leftNavControls = navControls.bySide[NavControlSide.Left]; const rightNavControls = navControls.bySide[NavControlSide.Right]; + let navLinksArray = navLinks.map(navLink => + navLink.hidden + ? null + : { + key: navLink.id, + label: navLink.title, + href: navLink.href, + iconType: navLink.euiIconType, + icon: + !navLink.euiIconType && navLink.icon ? ( + + ) : ( + undefined + ), + isActive: navLink.active, + 'data-test-subj': 'navDrawerAppsMenuLink', + } + ); + // filter out the null items + navLinksArray = navLinksArray.filter(item => item !== null); + + const recentLinksArray = [ + { + label: intl.formatMessage({ + id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsLabel', + defaultMessage: 'Recently viewed', + }), + iconType: 'clock', + isDisabled: recentlyAccessed.length > 0 ? false : true, + flyoutMenu: { + title: intl.formatMessage({ + id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle', + defaultMessage: 'Recent items', + }), + listItems: recentlyAccessed.map(item => ({ + label: truncateRecentItemLabel(item.label), + // TODO: Add what type of app/saved object to title attr + title: `${item.label}`, + 'aria-label': item.label, + href: item.href, + iconType: item.euiIconType, + })), + }, + }, + ]; + return ( @@ -238,85 +277,11 @@ class HeaderUI extends Component { - this.collapseDrawer()} - isDisabled={this.state.outsideClickDisabled} - > - - - - this.expandFlyout()} - isDisabled={recentlyAccessed.length > 0 ? false : true} - extraAction={{ - color: 'subdued', - iconType: 'arrowRight', - iconSize: 's', - 'aria-label': 'Expand to view recent apps and objects', - onClick: () => this.expandFlyout(), - alwaysShow: true, - }} - /> - - - - {navLinks.map(navLink => - navLink.hidden ? null : ( - - ) - )} - - - ({ - label: item.label, - href: item.href, - iconType: item.euiIconType, - size: 's', - style: { color: 'inherit' }, - 'aria-label': item.label, - }))} - onMouseLeave={this.collapseFlyout} - wrapText={true} - /> - - + + + + + ); } @@ -361,120 +326,6 @@ class HeaderUI extends Component { event.stopPropagation(); } }; - - private toggleOpen = () => { - this.setState({ - mobileIsHidden: !this.state.mobileIsHidden, - }); - - setTimeout(() => { - this.setState({ - outsideClickDisabled: this.state.mobileIsHidden ? true : false, - }); - }, this.getTimeoutMs(350)); - }; - - private expandDrawer = () => { - this.timeoutExpand = setTimeout(() => { - this.setState({ - isCollapsed: false, - }); - }, this.getTimeoutMs(750)); - - this.timeoutScrollbar = setTimeout(() => { - this.setState({ - showScrollbar: true, - }); - }, this.getTimeoutMs(1200)); - - // This prevents the drawer from collapsing when tabbing through children - // by clearing the timeout thus cancelling the onBlur event (see focusOut). - // This means isManagingFocus remains true as long as a child element - // has focus. This is the case since React bubbles up onFocus and onBlur - // events from the child elements. - - if (this.timeoutID) { - clearTimeout(this.timeoutID); - } - - if (!this.state.isManagingFocus) { - this.setState({ - isManagingFocus: true, - }); - } - }; - - private collapseDrawer = () => { - // Stop the expand animation - if (this.timeoutExpand) { - clearTimeout(this.timeoutExpand); - } - - if (this.timeoutScrollbar) { - clearTimeout(this.timeoutScrollbar); - } - - this.setState({ - flyoutIsAnimating: false, - isCollapsed: true, - flyoutIsCollapsed: true, - mobileIsHidden: true, - showScrollbar: false, - outsideClickDisabled: true, - }); - - // Scrolls the menu and flyout back to top when the nav drawer collapses - const menuEl = document.getElementById('navDrawerMenu'); - if (menuEl) { - menuEl.scrollTop = 0; - } - - const flyoutEl = document.getElementById('navDrawerFlyout'); - if (flyoutEl) { - flyoutEl.scrollTop = 0; - } - }; - - private focusOut = () => { - // This collapses the drawer when no children have focus (i.e. tabbed out). - // In other words, if focus does not bubble up from a child element, then - // the drawer will collapse. See the corresponding block in expandDrawer - // (called by onFocus) which cancels this operation via clearTimeout. - this.timeoutID = setTimeout(() => { - if (this.state.isManagingFocus) { - this.setState({ - isManagingFocus: false, - }); - - this.collapseDrawer(); - } - }, 0); - }; - - private expandFlyout = () => { - this.setState(() => ({ - flyoutIsCollapsed: !this.state.flyoutIsCollapsed, - })); - - this.setState({ - flyoutIsAnimating: true, - }); - }; - - private collapseFlyout = () => { - this.setState({ flyoutIsAnimating: true }); - - setTimeout(() => { - this.setState({ - flyoutIsCollapsed: true, - }); - }, this.getTimeoutMs(250)); - }; - - private getTimeoutMs = (defaultTimeout: number) => { - const uiSettings = chrome.getUiSettingsClient(); - return uiSettings.get('accessibility:disableAnimations') ? 0 : defaultTimeout; - }; } export const Header = injectI18n(HeaderUI); diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index eca064ef04646..8c2ef2ea37a13 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -283,7 +283,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); await pieChart.expectPieSliceCount(5); - await PageObjects.visualize.saveVisualization('Rendering Test: animal sounds pie'); + await PageObjects.visualize.saveVisualizationExpectSuccess('Rendering Test: animal sounds pie'); await PageObjects.header.clickDashboard(); await pieChart.expectPieSliceCount(5); diff --git a/test/functional/services/apps_menu.ts b/test/functional/services/apps_menu.ts index 3926bca016815..39a50adc1fd24 100644 --- a/test/functional/services/apps_menu.ts +++ b/test/functional/services/apps_menu.ts @@ -22,22 +22,19 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function AppsMenuProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); - const retry = getService('retry'); - const globalNav = getService('globalNav'); return new class AppsMenu { /** * Get the text and href from each of the links in the apps menu */ public async readLinks() { - await this.ensureMenuOpen(); - const appMenu = await testSubjects.find('navDrawer&expanded appsMenu'); + const appMenu = await testSubjects.find('navDrawer'); const $ = await appMenu.parseDomContent(); const links: Array<{ text: string; href: string; - }> = $.findTestSubjects('appLink') + }> = $.findTestSubjects('navDrawerAppsMenuLink') .toArray() .map((link: any) => { return { @@ -46,7 +43,6 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) { }; }); - await this.ensureMenuClosed(); return links; } @@ -65,30 +61,12 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) { public async clickLink(name: string) { try { log.debug(`click "${name}" app link`); - await this.ensureMenuOpen(); - const container = await testSubjects.find('navDrawer&expanded appsMenu'); - const link = await container.findByPartialLinkText(name); + const container = await testSubjects.find('navDrawer'); + // Text content is not visible or selectable (0px width) so we use an attr with th same value + const link = await container.findByCssSelector(`[aria-label='${name}']`); await link.click(); } finally { - await this.ensureMenuClosed(); - } - } - - private async ensureMenuClosed() { - await globalNav.moveMouseToLogo(); - await retry.waitFor( - 'apps drawer closed', - async () => await testSubjects.exists('navDrawer&collapsed') - ); - } - - private async ensureMenuOpen() { - if (!(await testSubjects.exists('navDrawer&expanded'))) { - await testSubjects.moveMouseTo('navDrawer'); - await retry.waitFor( - 'apps drawer open', - async () => await testSubjects.exists('navDrawer&expanded') - ); + // Intentionally empty } } }(); diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 228ce357f16eb..181865ed3ac52 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "5.0.0", - "react": "^16.3.0" + "@elastic/eui": "9.4.0", + "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index 6f0ce8eaea878..ae68ac4f5c543 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "5.0.0", - "react": "^16.3.0" + "@elastic/eui": "9.4.0", + "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json index eb67a8a876c5d..6286abc45f06f 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json @@ -7,8 +7,8 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "5.0.0", - "react": "^16.3.0", - "react-dom": "^16.3.0" + "@elastic/eui": "9.4.0", + "react": "^16.8.0", + "react-dom": "^16.8.0" } } diff --git a/test/plugin_functional/test_suites/app_plugins/app_navigation.js b/test/plugin_functional/test_suites/app_plugins/app_navigation.js index 7d4eddda4bcd0..1adcde72e1aa3 100644 --- a/test/plugin_functional/test_suites/app_plugins/app_navigation.js +++ b/test/plugin_functional/test_suites/app_plugins/app_navigation.js @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.common.navigateToApp('settings'); }); - it('should should nav link that navigates to the app', async () => { + it('should show nav link that navigates to the app', async () => { await appsMenu.clickLink('Test Plugin App'); const pluginContent = await testSubjects.find('pluginContent'); expect(await pluginContent.getVisibleText()).to.be('Super simple app plugin'); diff --git a/x-pack/package.json b/x-pack/package.json index 5c8dfe30743c2..47220b20cfc99 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -136,7 +136,7 @@ }, "dependencies": { "@elastic/datemath": "5.0.2", - "@elastic/eui": "9.2.1", + "@elastic/eui": "9.4.0", "@elastic/node-crypto": "0.1.2", "@elastic/numeral": "2.3.2", "@kbn/babel-preset": "1.0.0", diff --git a/yarn.lock b/yarn.lock index b41852c21f72c..fa12fb64aea2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -796,34 +796,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-5.0.0.tgz#e6fe9e1aa8b00c93045178f78a6dd0d457d56fa8" - integrity sha512-WL6sp6u2Rt1O7a2exLU/RuDcRnpluPN6aQ2JexBl+G6mVyF8F5I3RGJKTJp3jOozOaODRY2ev+Nq57EydkjrKg== - dependencies: - classnames "^2.2.5" - core-js "^2.5.1" - focus-trap-react "^3.0.4" - highlight.js "^9.12.0" - html "^1.0.0" - keymirror "^0.1.1" - lodash "npm:@elastic/lodash@3.10.1-kibana1" - numeral "^2.0.6" - prop-types "^15.6.0" - react-ace "^5.5.0" - react-color "^2.13.8" - react-datepicker v1.5.0 - react-input-autosize "^2.2.1" - react-virtualized "^9.18.5" - react-vis "1.10.2" - resize-observer-polyfill "^1.5.0" - tabbable "^1.1.0" - uuid "^3.1.0" - -"@elastic/eui@9.2.1": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.2.1.tgz#3860a7219f0ee8f33ec43447edb2eda3c96a00a7" - integrity sha512-U92s0nh6vBS6NBiHDCq6e+49Dam8It+Iy81b2NProf1OIRk/nB0RE781x6zgUzmOFpJ1T0xiThWiiDP776v0LQ== +"@elastic/eui@9.4.0": + version "9.4.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.4.0.tgz#e5f83c612674fc6f59e990f557e563be1409d25e" + integrity sha512-cQnsU3UeQ1s6F427vY53lzlh7mvNoPmIvPL24B5jH3JYaWULhIL1xSJXJgDSOSdrdHCxkXDD9Q8Y7256x4Xf4Q== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" @@ -18132,11 +18108,6 @@ polished@^1.9.2: resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.2.tgz#d705cac66f3a3ed1bd38aad863e2c1e269baf6b6" integrity sha512-mPocQrVUSiqQdHNZFGL1iHJmsR/etiv05Nf2oZUbya+GMsQkZVEBl5wonN+Sr/e9zQBEhT6yrMjxAUJ06eyocQ== -popper.js@^1.14.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" - integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU= - portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -18997,16 +18968,6 @@ react-color@^2.13.8, react-color@^2.14.1: reactcss "^1.2.0" tinycolor2 "^1.4.1" -react-datepicker@v1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-1.5.0.tgz#7eacd9609313189c84a21bb7421486054939a4b2" - integrity sha512-Neh1rz0d1QeR7KuoTiYeR6oj73DJkqt0vuNSgfMuxXEwGmz/4sPynouYGo6gdKiQbxIXBJJ/FLDLHJEr5XNThw== - dependencies: - classnames "^2.2.5" - prop-types "^15.6.0" - react-onclickoutside "^6.7.1" - react-popper "^0.9.1" - react-datetime@^2.14.0: version "2.15.0" resolved "https://registry.yarnpkg.com/react-datetime/-/react-datetime-2.15.0.tgz#a8f7da6c58b6b45dbeea32d4e8485db17614e12c" @@ -19087,7 +19048,7 @@ react-docgen@^3.0.0-rc.1: node-dir "^0.1.10" recast "^0.16.0" -react-dom@^16.2.0, react-dom@^16.3.0, react-dom@^16.8.0: +react-dom@^16.2.0, react-dom@^16.8.0: version "16.8.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.2.tgz#7c8a69545dd554d45d66442230ba04a6a0a3c3d3" integrity sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg== @@ -19258,19 +19219,11 @@ react-motion@^0.5.2: prop-types "^15.5.8" raf "^3.1.0" -react-onclickoutside@^6.5.0, react-onclickoutside@^6.7.1: +react-onclickoutside@^6.5.0: version "6.7.1" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz#6a5b5b8b4eae6b776259712c89c8a2b36b17be93" integrity sha512-p84kBqGaMoa7VYT0vZ/aOYRfJB+gw34yjpda1Z5KeLflg70HipZOT+MXQenEhdkPAABuE2Astq4zEPdMqUQxcg== -react-popper@^0.9.1: - version "0.9.5" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.9.5.tgz#02a24ef3eec33af9e54e8358ab70eb0e331edd05" - integrity sha1-AqJO8+7DOvnlToNYq3DrDjMe3QU= - dependencies: - popper.js "^1.14.1" - prop-types "^15.6.1" - react-portal@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-3.2.0.tgz#4224e19b2b05d5cbe730a7ba0e34ec7585de0043" @@ -19536,7 +19489,7 @@ react-vis@^1.8.1: prop-types "^15.5.8" react-motion "^0.4.8" -react@^16.2.0, react@^16.3.0, react@^16.6.0, react@^16.8.0: +react@^16.2.0, react@^16.6.0, react@^16.8.0: version "16.8.2" resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c" integrity sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==