diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 5fda9f91593061c..43b530b455816d4 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -57,6 +57,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | | | [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | | | [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | +| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | | | [OverlayRef](./kibana-plugin-public.overlayref.md) | | | [OverlayStart](./kibana-plugin-public.overlaystart.md) | | | [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a PluginInitializer. | @@ -88,6 +89,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpStart](./kibana-plugin-public.httpstart.md) | | | [IContextHandler](./kibana-plugin-public.icontexthandler.md) | A function registered by a plugin to perform some action. | | [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | +| [OverlayBannerMount](./kibana-plugin-public.overlaybannermount.md) | A function that will mount the banner inside the provided element. | +| [OverlayBannerUnmount](./kibana-plugin-public.overlaybannerunmount.md) | A function that will unmount the banner from the element. | | [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | | | [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | | diff --git a/docs/development/core/public/kibana-plugin-public.overlaybannermount.md b/docs/development/core/public/kibana-plugin-public.overlaybannermount.md new file mode 100644 index 000000000000000..0fd0aca652cf0b6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaybannermount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayBannerMount](./kibana-plugin-public.overlaybannermount.md) + +## OverlayBannerMount type + +A function that will mount the banner inside the provided element. + +Signature: + +```typescript +export declare type OverlayBannerMount = (element: HTMLElement) => OverlayBannerUnmount; +``` diff --git a/docs/development/core/public/kibana-plugin-public.overlaybannersstart.add.md b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.add.md new file mode 100644 index 000000000000000..e1f8670f5c38b81 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.add.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) > [add](./kibana-plugin-public.overlaybannersstart.add.md) + +## OverlayBannersStart.add() method + +Add a new banner + +Signature: + +```typescript +add(mount: OverlayBannerMount, priority?: number): symbol; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mount | OverlayBannerMount | | +| priority | number | | + +Returns: + +`symbol` + +a unique symbol for the given banner to be used with [OverlayBannersStart.remove()](./kibana-plugin-public.overlaybannersstart.remove.md) and [OverlayBannersStart.replace()](./kibana-plugin-public.overlaybannersstart.replace.md) + diff --git a/docs/development/core/public/kibana-plugin-public.overlaybannersstart.md b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.md new file mode 100644 index 000000000000000..8985acde4caaa2d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) + +## OverlayBannersStart interface + + +Signature: + +```typescript +export interface OverlayBannersStart +``` + +## Methods + +| Method | Description | +| --- | --- | +| [add(mount, priority)](./kibana-plugin-public.overlaybannersstart.add.md) | Add a new banner | +| [remove(id)](./kibana-plugin-public.overlaybannersstart.remove.md) | Remove a banner | +| [replace(id, mount, priority)](./kibana-plugin-public.overlaybannersstart.replace.md) | Replace a banner in place | + diff --git a/docs/development/core/public/kibana-plugin-public.overlaybannersstart.remove.md b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.remove.md new file mode 100644 index 000000000000000..53accbac2a1206c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.remove.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) > [remove](./kibana-plugin-public.overlaybannersstart.remove.md) + +## OverlayBannersStart.remove() method + +Remove a banner + +Signature: + +```typescript +remove(id: symbol): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | symbol | | + +Returns: + +`boolean` + +if the banner was found or not + diff --git a/docs/development/core/public/kibana-plugin-public.overlaybannersstart.replace.md b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.replace.md new file mode 100644 index 000000000000000..7ceba91ceb8063e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaybannersstart.replace.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) > [replace](./kibana-plugin-public.overlaybannersstart.replace.md) + +## OverlayBannersStart.replace() method + +Replace a banner in place + +Signature: + +```typescript +replace(id: symbol, mount: OverlayBannerMount, priority?: number): symbol; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | symbol | | +| mount | OverlayBannerMount | | +| priority | number | | + +Returns: + +`symbol` + +a new symbol for the given banner to be used with [OverlayBannersStart.remove()](./kibana-plugin-public.overlaybannersstart.remove.md) and [OverlayBannersStart.replace()](./kibana-plugin-public.overlaybannersstart.replace.md) + diff --git a/docs/development/core/public/kibana-plugin-public.overlaybannerunmount.md b/docs/development/core/public/kibana-plugin-public.overlaybannerunmount.md new file mode 100644 index 000000000000000..c9a7c2b8fee9299 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaybannerunmount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayBannerUnmount](./kibana-plugin-public.overlaybannerunmount.md) + +## OverlayBannerUnmount type + +A function that will unmount the banner from the element. + +Signature: + +```typescript +export declare type OverlayBannerUnmount = () => void; +``` diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.banners.md b/docs/development/core/public/kibana-plugin-public.overlaystart.banners.md new file mode 100644 index 000000000000000..60ecc4b873f0d86 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.banners.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayStart](./kibana-plugin-public.overlaystart.md) > [banners](./kibana-plugin-public.overlaystart.banners.md) + +## OverlayStart.banners property + +[OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) + +Signature: + +```typescript +banners: OverlayBannersStart; +``` diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.md b/docs/development/core/public/kibana-plugin-public.overlaystart.md index 1345beffbfb6ac6..6bcf0a581df8001 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.md @@ -15,6 +15,7 @@ export interface OverlayStart | Property | Type | Description | | --- | --- | --- | +| [banners](./kibana-plugin-public.overlaystart.banners.md) | OverlayBannersStart | [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | | [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | (flyoutChildren: React.ReactNode, flyoutProps?: {
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef | | | [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | (modalChildren: React.ReactNode, modalProps?: {
className?: string;
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
}) => OverlayRef | | diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 7782c93c7bbb1bc..b0429099d0867dd 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -216,6 +216,7 @@ export class CoreSystem { const plugins = await this.plugins.start(core); const rendering = this.rendering.start({ chrome, + overlays, targetDomElement: coreUiTargetDomElement, }); await this.legacyPlatform.start({ diff --git a/src/core/public/index.scss b/src/core/public/index.scss index 42a25aa686696af..86f2efdff77020c 100644 --- a/src/core/public/index.scss +++ b/src/core/public/index.scss @@ -8,3 +8,4 @@ @import '@elastic/eui/src/global_styling/mixins/index'; @import './chrome/index'; +@import './overlays/index'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index abc922ff97c1d54..ff3413eeeedc1f7 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -61,7 +61,7 @@ import { ToastInput, ToastsApi, } from './notifications'; -import { OverlayRef, OverlayStart } from './overlays'; +import { OverlayStart } from './overlays'; import { Plugin, PluginInitializer, PluginInitializerContext, PluginOpaqueId } from './plugins'; import { UiSettingsClient, UiSettingsState, UiSettingsClientContract } from './ui_settings'; import { ApplicationSetup, Capabilities, ApplicationStart } from './application'; @@ -104,6 +104,14 @@ export { HttpBody, } from './http'; +export { + OverlayStart, + OverlayBannerMount, + OverlayBannerUnmount, + OverlayBannersStart, + OverlayRef, +} from './overlays'; + /** * Core services exposed to the `Plugin` setup lifecycle * @@ -198,8 +206,6 @@ export { LegacyNavLink, NotificationsSetup, NotificationsStart, - OverlayRef, - OverlayStart, Plugin, PluginInitializer, PluginInitializerContext, diff --git a/src/legacy/ui/public/notify/_index.scss b/src/core/public/overlays/_index.scss similarity index 100% rename from src/legacy/ui/public/notify/_index.scss rename to src/core/public/overlays/_index.scss diff --git a/src/legacy/ui/public/notify/banners/_global_banner_list.scss b/src/core/public/overlays/banners/_banners_list.scss similarity index 100% rename from src/legacy/ui/public/notify/banners/_global_banner_list.scss rename to src/core/public/overlays/banners/_banners_list.scss diff --git a/src/core/public/overlays/banners/_index.scss b/src/core/public/overlays/banners/_index.scss new file mode 100644 index 000000000000000..c0c8056ff5d7d5c --- /dev/null +++ b/src/core/public/overlays/banners/_index.scss @@ -0,0 +1 @@ +@import './banners_list'; diff --git a/src/core/public/overlays/banners/banners_list.tsx b/src/core/public/overlays/banners/banners_list.tsx new file mode 100644 index 000000000000000..29b78250296f892 --- /dev/null +++ b/src/core/public/overlays/banners/banners_list.tsx @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useRef, useState } from 'react'; +import { Observable } from 'rxjs'; + +import { OverlayBanner } from './banners_service'; + +interface Props { + banners$: Observable; +} + +/** + * BannersList is a list of "banners". A banner something that is displayed at the top of Kibana that may or may not + * disappear. + * + * Whether or not a banner can be closed is completely up to the author of the banner. Some banners make sense to be + * static, such as banners meant to indicate the sensitivity (e.g., classification) of the information being + * represented. + */ +export const BannersList: React.FunctionComponent = ({ banners$ }) => { + const [banners, setBanners] = useState([]); + useEffect(() => { + const subscription = banners$.subscribe(setBanners); + return () => subscription.unsubscribe(); + }, [banners$]); // Only un/re-subscribe if the Observable changes + + if (banners.length === 0) { + return null; + } + + return ( +
+ {banners.map((banner, idx) => ( + + ))} +
+ ); +}; + +const BannerItem: React.FunctionComponent<{ banner: OverlayBanner }> = ({ banner }) => { + const element = useRef(null); + useEffect(() => banner.mount(element.current!)); + + return ( +
+ ); +}; diff --git a/src/core/public/overlays/banners/banners_service.tsx b/src/core/public/overlays/banners/banners_service.tsx new file mode 100644 index 000000000000000..d53a8fd3f0d39d1 --- /dev/null +++ b/src/core/public/overlays/banners/banners_service.tsx @@ -0,0 +1,136 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { sortBy } from 'lodash'; +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { BannersList } from './banners_list'; + +/** + * A function that will unmount the banner from the element. + * @public + */ +export type OverlayBannerUnmount = () => void; + +/** + * A function that will mount the banner inside the provided element. + * @param element an element to render into + * @returns a {@link OverlayBannerUnmount} + * @public + */ +export type OverlayBannerMount = (element: HTMLElement) => OverlayBannerUnmount; + +/** @public */ +export interface OverlayBannersStart { + /** + * Add a new banner + * + * @param mount + * @param priority + * @returns a unique symbol for the given banner to be used with {@link OverlayBannersStart.remove} and + * {@link OverlayBannersStart.replace} + */ + add(mount: OverlayBannerMount, priority?: number): symbol; + + /** + * Remove a banner + * + * @param id the unique symbol for the banner returned by {@link OverlayBannersStart.add} + * @returns if the banner was found or not + */ + remove(id: symbol): boolean; + + /** + * Replace a banner in place + * + * @param id the unique symbol for the banner returned by {@link OverlayBannersStart.add} + * @param mount + * @param priority + * @returns a new symbol for the given banner to be used with {@link OverlayBannersStart.remove} and + * {@link OverlayBannersStart.replace} + */ + replace(id: symbol, mount: OverlayBannerMount, priority?: number): symbol; + + /** @internal */ + getComponent(): JSX.Element; +} + +/** @internal */ +export interface OverlayBanner { + id: symbol; + mount: OverlayBannerMount; + priority: number; +} + +/** @internal */ +export class OverlayBannersService { + public start(): OverlayBannersStart { + const banners$ = new BehaviorSubject>(new Map()); + + const addToMap = (banners: ReadonlyMap, bannerToAdd: OverlayBanner) => { + return new Map( + // Filter by descending priority + sortBy([...banners.values(), bannerToAdd], 'priority') + .reverse() + .map(banner => [banner.id, banner]) + ); + }; + + const removeFromMap = (banners: ReadonlyMap, id: symbol) => { + return new Map([...banners].filter(([bannerId]) => bannerId !== id)); + }; + + return { + add: (mount, priority = 0) => { + const id = Symbol(); + const nextBanner: OverlayBanner = { id, mount, priority }; + banners$.next(addToMap(banners$.value, nextBanner)); + return id; + }, + + remove: (id: symbol) => { + if (!banners$.value.has(id)) { + return false; + } + + banners$.next(removeFromMap(banners$.value, id)); + + return true; + }, + + replace(id: symbol, mount: OverlayBannerMount, priority = 0) { + if (!banners$.value.has(id)) { + return this.add(mount, priority); + } + + const nextId = Symbol(); + const nextBanner = { id: nextId, mount, priority }; + + banners$.next(addToMap(removeFromMap(banners$.value, id), nextBanner)); + return nextId; + }, + + getComponent() { + return [...bannerMap.values()]))} />; + }, + }; + } +} diff --git a/src/core/public/overlays/banners/index.ts b/src/core/public/overlays/banners/index.ts new file mode 100644 index 000000000000000..9e908bd62800383 --- /dev/null +++ b/src/core/public/overlays/banners/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + OverlayBannerMount, + OverlayBannerUnmount, + OverlayBannersStart, + OverlayBannersService, +} from './banners_service'; diff --git a/src/core/public/overlays/index.ts b/src/core/public/overlays/index.ts index 6e92b0e84f8a0a6..c49548abee0df3d 100644 --- a/src/core/public/overlays/index.ts +++ b/src/core/public/overlays/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export { OverlayBannerMount, OverlayBannerUnmount, OverlayBannersStart } from './banners'; export { OverlayService, OverlayStart, OverlayRef } from './overlay_service'; diff --git a/src/core/public/overlays/overlay_service.ts b/src/core/public/overlays/overlay_service.ts index a9c44f63013c793..e6e658bce54ac36 100644 --- a/src/core/public/overlays/overlay_service.ts +++ b/src/core/public/overlays/overlay_service.ts @@ -22,6 +22,7 @@ import React from 'react'; import { FlyoutService } from './flyout'; import { ModalService } from './modal'; import { I18nStart } from '../i18n'; +import { OverlayBannersStart, OverlayBannersService } from './banners'; export interface OverlayRef { /** @@ -47,26 +48,27 @@ interface StartDeps { /** @internal */ export class OverlayService { - private flyoutService?: FlyoutService; - private modalService?: ModalService; - public start({ i18n, targetDomElement }: StartDeps): OverlayStart { const flyoutElement = document.createElement('div'); const modalElement = document.createElement('div'); targetDomElement.appendChild(flyoutElement); targetDomElement.appendChild(modalElement); - this.flyoutService = new FlyoutService(flyoutElement); - this.modalService = new ModalService(modalElement); + const flyoutService = new FlyoutService(flyoutElement); + const modalService = new ModalService(modalElement); + const bannersService = new OverlayBannersService(); return { - openFlyout: this.flyoutService.openFlyout.bind(this.flyoutService, i18n), - openModal: this.modalService.openModal.bind(this.modalService, i18n), + banners: bannersService.start(), + openFlyout: flyoutService.openFlyout.bind(flyoutService, i18n), + openModal: modalService.openModal.bind(modalService, i18n), }; } } /** @public */ export interface OverlayStart { + /** {@link OverlayBannersStart} */ + banners: OverlayBannersStart; openFlyout: ( flyoutChildren: React.ReactNode, flyoutProps?: { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index a5c31e41e026729..6b5ca4349611632 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -554,6 +554,21 @@ export interface NotificationsStart { toasts: ToastsStart; } +// @public +export type OverlayBannerMount = (element: HTMLElement) => OverlayBannerUnmount; + +// @public (undocumented) +export interface OverlayBannersStart { + add(mount: OverlayBannerMount, priority?: number): symbol; + // @internal (undocumented) + getComponent(): JSX.Element; + remove(id: symbol): boolean; + replace(id: symbol, mount: OverlayBannerMount, priority?: number): symbol; +} + +// @public +export type OverlayBannerUnmount = () => void; + // Warning: (ae-missing-release-tag) "OverlayRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -564,6 +579,8 @@ export interface OverlayRef { // @public (undocumented) export interface OverlayStart { + // (undocumented) + banners: OverlayBannersStart; // (undocumented) openFlyout: (flyoutChildren: React.ReactNode, flyoutProps?: { closeButtonAriaLabel?: string; diff --git a/src/core/public/rendering/rendering_service.tsx b/src/core/public/rendering/rendering_service.tsx index cbb931bf59ef90f..447b3e270e55b87 100644 --- a/src/core/public/rendering/rendering_service.tsx +++ b/src/core/public/rendering/rendering_service.tsx @@ -22,9 +22,11 @@ import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; import { InternalChromeStart } from '../chrome'; +import { OverlayStart } from '../overlays'; interface StartDeps { chrome: InternalChromeStart; + overlays: OverlayStart; targetDomElement: HTMLDivElement; } @@ -39,8 +41,9 @@ interface StartDeps { * @internal */ export class RenderingService { - start({ chrome, targetDomElement }: StartDeps) { + start({ chrome, overlays, targetDomElement }: StartDeps) { const chromeUi = chrome.getComponent(); + const bannerUi = overlays.banners.getComponent(); const legacyRef = React.createRef(); ReactDOM.render( @@ -48,7 +51,13 @@ export class RenderingService {
{chromeUi} -
+
+
+
{bannerUi}
+ +
+
+
, targetDomElement diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index 1c3a9c006acfd0d..0276414d7351d3c 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -18,7 +18,6 @@ @import './error_url_overflow/index'; @import './exit_full_screen/index'; @import './field_editor/index'; -@import './notify/index'; @import './share/index'; @import './style_compile/index'; diff --git a/src/legacy/ui/public/chrome/chrome.js b/src/legacy/ui/public/chrome/chrome.js index 8f58da9107673fd..0b0a0e7944ce4e1 100644 --- a/src/legacy/ui/public/chrome/chrome.js +++ b/src/legacy/ui/public/chrome/chrome.js @@ -97,8 +97,7 @@ const waitForBootstrap = new Promise(resolve => { chrome.setupAngular(); // targetDomElement.setAttribute('id', 'kibana-body'); targetDomElement.setAttribute('kbn-chrome', 'true'); - targetDomElement.setAttribute('ng-class', '{ \'hidden-chrome\': !chrome.getVisible() }'); - targetDomElement.className = 'app-wrapper'; + // targetDomElement.setAttribute('ng-class', '{ \'hidden-chrome\': !chrome.getVisible() }'); angular.bootstrap(targetDomElement, ['kibana']); resolve(targetDomElement); }; diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.html b/src/legacy/ui/public/chrome/directives/kbn_chrome.html index 541082e68de58d2..276f3b2e19f91e3 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.html +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.html @@ -1,9 +1,5 @@ -
-
- -
-
+
diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.js b/src/legacy/ui/public/chrome/directives/kbn_chrome.js index d81a1ceb5f288eb..a5190e7ff772e0f 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js @@ -17,19 +17,11 @@ * under the License. */ -import React from 'react'; -import ReactDOM from 'react-dom'; import $ from 'jquery'; import { uiModules } from '../../modules'; import template from './kbn_chrome.html'; -import { - GlobalBannerList, - banners, -} from '../../notify'; - -import { I18nContext } from '../../i18n'; import { npStart } from '../../new_platform'; import { chromeHeaderNavControlsRegistry, NavControlSide } from '../../registry/chrome_header_nav_controls'; @@ -40,8 +32,7 @@ export function kbnChromeProvider(chrome, internals) { .directive('kbnChrome', () => { return { template() { - const $content = $(template); - const $app = $content.find('.application'); + const $app = $(template); if (internals.rootController) { $app.attr('ng-controller', internals.rootController); @@ -52,7 +43,7 @@ export function kbnChromeProvider(chrome, internals) { $app.html(internals.rootTemplate); } - return $content; + return $app; }, controllerAs: 'chrome', @@ -74,19 +65,6 @@ export function kbnChromeProvider(chrome, internals) { mount: navControl.render, })); - // Non-scope based code (e.g., React) - - // Banners - ReactDOM.render( - - - , - document.getElementById('globalBannerList') - ); - return chrome; } }; diff --git a/src/legacy/ui/public/notify/banners/__snapshots__/global_banner_list.test.js.snap b/src/legacy/ui/public/notify/banners/__snapshots__/global_banner_list.test.js.snap deleted file mode 100644 index fd1407670b452d5..000000000000000 --- a/src/legacy/ui/public/notify/banners/__snapshots__/global_banner_list.test.js.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GlobalBannerList is rendered 1`] = `null`; - -exports[`GlobalBannerList props banners is rendered 1`] = ` -
-
- a component -
-
- b good -
-
-`; diff --git a/src/legacy/ui/public/notify/banners/_index.scss b/src/legacy/ui/public/notify/banners/_index.scss deleted file mode 100644 index 96b9a42385e86e2..000000000000000 --- a/src/legacy/ui/public/notify/banners/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './global_banner_list'; diff --git a/src/legacy/ui/public/notify/banners/banners.test.js b/src/legacy/ui/public/notify/banners/banners.test.js deleted file mode 100644 index 69b046bcb4d8fc6..000000000000000 --- a/src/legacy/ui/public/notify/banners/banners.test.js +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; - -import { - Banners, -} from './banners'; - -describe('Banners', () => { - - describe('interface', () => { - let banners; - - beforeEach(() => { - banners = new Banners(); - }); - - describe('onChange method', () => { - - test('callback is called when a banner is added', () => { - const onChangeSpy = sinon.spy(); - banners.onChange(onChangeSpy); - banners.add({ component: 'bruce-banner' }); - expect(onChangeSpy.callCount).toBe(1); - }); - - test('callback is called when a banner is removed', () => { - const onChangeSpy = sinon.spy(); - banners.onChange(onChangeSpy); - banners.remove(banners.add({ component: 'bruce-banner' })); - expect(onChangeSpy.callCount).toBe(2); - }); - - test('callback is not called when remove is ignored', () => { - const onChangeSpy = sinon.spy(); - banners.onChange(onChangeSpy); - banners.remove('hulk'); // should not invoke callback - expect(onChangeSpy.callCount).toBe(0); - }); - - test('callback is called once when banner is replaced', () => { - const onChangeSpy = sinon.spy(); - banners.onChange(onChangeSpy); - const addBannerId = banners.add({ component: 'bruce-banner' }); - banners.set({ id: addBannerId, component: 'hulk' }); - expect(onChangeSpy.callCount).toBe(2); - }); - - }); - - describe('add method', () => { - - test('adds a banner', () => { - const id = banners.add({}); - expect(banners.list.length).toBe(1); - expect(id).toEqual(expect.stringMatching(/^\d+$/)); - }); - - test('adds a banner and ignores an ID property', () => { - const bannerId = banners.add({ id: 'bruce-banner' }); - expect(banners.list[0].id).toBe(bannerId); - expect(bannerId).not.toBe('bruce-banner'); - }); - - test('sorts banners based on priority', () => { - const test0 = banners.add({ }); - // the fact that it was set explicitly is irrelevant; that it was added second means it should be after test0 - const test0Explicit = banners.add({ priority: 0 }); - const test1 = banners.add({ priority: 1 }); - const testMinus1 = banners.add({ priority: -1 }); - const test1000 = banners.add({ priority: 1000 }); - - expect(banners.list.length).toBe(5); - expect(banners.list[0].id).toBe(test1000); - expect(banners.list[1].id).toBe(test1); - expect(banners.list[2].id).toBe(test0); - expect(banners.list[3].id).toBe(test0Explicit); - expect(banners.list[4].id).toBe(testMinus1); - }); - - }); - - describe('remove method', () => { - - test('removes a banner', () => { - const bannerId = banners.add({ component: 'bruce-banner' }); - banners.remove(bannerId); - expect(banners.list.length).toBe(0); - }); - - test('ignores unknown id', () => { - banners.add({ component: 'bruce-banner' }); - banners.remove('hulk'); - expect(banners.list.length).toBe(1); - }); - - }); - - describe('set method', () => { - - test('replaces banners', () => { - const addBannerId = banners.add({ component: 'bruce-banner' }); - const setBannerId = banners.set({ id: addBannerId, component: 'hulk' }); - - expect(banners.list.length).toBe(1); - expect(banners.list[0].component).toBe('hulk'); - expect(banners.list[0].id).toBe(setBannerId); - expect(addBannerId).not.toBe(setBannerId); - }); - - test('ignores unknown id', () => { - const id = banners.set({ id: 'fake', component: 'hulk' }); - - expect(banners.list.length).toBe(1); - expect(banners.list[0].component).toBe('hulk'); - expect(banners.list[0].id).toBe(id); - }); - - test('replaces a banner with the same ID property', () => { - const test0 = banners.add({ }); - const test0Explicit = banners.add({ priority: 0 }); - let test1 = banners.add({ priority: 1, component: 'old' }); - const testMinus1 = banners.add({ priority: -1 }); - let test1000 = banners.add({ priority: 1000, component: 'old' }); - - // change one with the same priority - test1 = banners.set({ id: test1, priority: 1, component: 'new' }); - // change one with a different priority - test1000 = banners.set({ id: test1000, priority: 1, component: 'new' }); - - expect(banners.list.length).toBe(5); - expect(banners.list[0].id).toBe(test1); - expect(banners.list[0].component).toBe('new'); - expect(banners.list[1].id).toBe(test1000); // priority became 1, so it goes after the other "1" - expect(banners.list[1].component).toBe('new'); - expect(banners.list[2].id).toBe(test0); - expect(banners.list[3].id).toBe(test0Explicit); - expect(banners.list[4].id).toBe(testMinus1); - }); - - }); - - }); -}); diff --git a/src/legacy/ui/public/notify/banners/banners.js b/src/legacy/ui/public/notify/banners/banners.tsx similarity index 53% rename from src/legacy/ui/public/notify/banners/banners.js rename to src/legacy/ui/public/notify/banners/banners.tsx index 3f8dd08771d433d..31027b336d7eab7 100644 --- a/src/legacy/ui/public/notify/banners/banners.js +++ b/src/legacy/ui/public/notify/banners/banners.tsx @@ -17,89 +17,43 @@ * under the License. */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { npStart } from 'ui/new_platform'; +import { I18nProvider } from '@kbn/i18n/react'; + +const npBanners = npStart.core.overlays.banners; + +/** compatibility layer for new platform */ +const mountForComponent = (component: React.ReactElement) => (element: HTMLElement) => { + ReactDOM.render({component}, element); + return () => ReactDOM.unmountComponentAtNode(element); +}; + /** * Banners represents a prioritized list of displayed components. */ export class Banners { - - constructor() { - // sorted in descending order (100, 99, 98...) so that higher priorities are in front - this.list = []; - this.uniqueId = 0; - this.onChangeCallback = null; - } - - _changed = () => { - if (this.onChangeCallback) { - this.onChangeCallback(); - } - } - - _remove = id => { - const index = this.list.findIndex(details => details.id === id); - - if (index !== -1) { - this.list.splice(index, 1); - - return true; - } - - return false; - } - - /** - * Set the {@code callback} to invoke whenever changes are made to the banner list. - * - * Use {@code null} or {@code undefined} to unset it. - * - * @param {Function} callback The callback to use. - */ - onChange = callback => { - this.onChangeCallback = callback; - } - /** * Add a new banner. * * @param {Object} component The React component to display. * @param {Number} priority The optional priority order to display this banner. Higher priority values are shown first. - * @return {String} A newly generated ID. This value can be used to remove/replace the banner. + * @return {symbol} A newly generated ID. This value can be used to remove/replace the banner. */ - add = ({ component, priority = 0 }) => { - const id = `${++this.uniqueId}`; - const bannerDetails = { id, component, priority }; - - // find the lowest priority item to put this banner in front of - const index = this.list.findIndex(details => priority > details.priority); - - if (index !== -1) { - // we found something with a lower priority; so stick it in front of that item - this.list.splice(index, 0, bannerDetails); - } else { - // nothing has a lower priority, so put it at the end - this.list.push(bannerDetails); - } - - this._changed(); - - return id; - } + add = ({ component, priority }: { component: React.ReactElement; priority?: number }) => { + return npBanners.add(mountForComponent(component), priority); + }; /** * Remove an existing banner. * - * @param {String} id The ID of the banner to remove. + * @param {symbol} id The ID of the banner to remove. * @return {Boolean} {@code true} if the ID is recognized and the banner is removed. {@code false} otherwise. */ - remove = id => { - const removed = this._remove(id); - - if (removed) { - this._changed(); - } - - return removed; - } + remove = (id: symbol): boolean => { + return npBanners.remove(id); + }; /** * Replace an existing banner by removing it, if it exists, and adding a new one in its place. @@ -112,12 +66,17 @@ export class Banners { * @param {Number} priority The optional priority order to display this banner. Higher priority values are shown first. * @return {String} A newly generated ID. This value can be used to remove/replace the banner. */ - set = ({ component, id, priority = 0 }) => { - this._remove(id); - - return this.add({ component, priority }); - } - + set = ({ + component, + id, + priority = 0, + }: { + component: React.ReactElement; + id: symbol; + priority?: number; + }): symbol => { + return npBanners.replace(id, mountForComponent(component), priority); + }; } /** diff --git a/src/legacy/ui/public/notify/banners/global_banner_list.js b/src/legacy/ui/public/notify/banners/global_banner_list.js deleted file mode 100644 index 9a6f120968b0caf..000000000000000 --- a/src/legacy/ui/public/notify/banners/global_banner_list.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -/** - * GlobalBannerList is a list of "banners". A banner something that is displayed at the top of Kibana that may or may not disappear. - * - * Whether or not a banner can be closed is completely up to the author of the banner. Some banners make sense to be static, such as - * banners meant to indicate the sensitivity (e.g., classification) of the information being represented. - * - * Banners are currently expected to be instances, but that is not required. - * - * @param {Array} banners The array of banners represented by objects in the form of { id, component }. - */ -export class GlobalBannerList extends Component { - static propTypes = { - banners: PropTypes.array, - subscribe: PropTypes.func, - }; - - static defaultProps = { - banners: [], - }; - - constructor(props) { - super(props); - - if (this.props.subscribe) { - this.props.subscribe(() => this.forceUpdate()); - } - } - - render() { - if (this.props.banners.length === 0) { - return null; - } - - const flexBanners = this.props.banners.map(banner => { - const { id, component, priority, ...rest } = banner; - - return ( -
- {component} -
- ); - }); - - return
{flexBanners}
; - } -} diff --git a/src/legacy/ui/public/notify/banners/global_banner_list.test.js b/src/legacy/ui/public/notify/banners/global_banner_list.test.js deleted file mode 100644 index 5a008f33c57d942..000000000000000 --- a/src/legacy/ui/public/notify/banners/global_banner_list.test.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render } from 'enzyme'; -import { GlobalBannerList } from './global_banner_list'; - -describe('GlobalBannerList', () => { - - test('is rendered', () => { - const component = render( - - ); - - expect(component) - .toMatchSnapshot(); - - }); - - describe('props', () => { - - describe('banners', () => { - - test('is rendered', () => { - const banners = [{ - id: 'a', - component: 'a component', - priority: 1, - }, { - 'data-test-subj': 'b', - id: 'b', - component: 'b good', - }]; - - const component = render( - - ); - - expect(component) - .toMatchSnapshot(); - }); - - }); - - }); - -}); diff --git a/src/legacy/ui/public/notify/banners/index.js b/src/legacy/ui/public/notify/banners/index.js index ad2fcd650e5c0ac..9221f95074cd964 100644 --- a/src/legacy/ui/public/notify/banners/index.js +++ b/src/legacy/ui/public/notify/banners/index.js @@ -17,5 +17,4 @@ * under the License. */ -export { GlobalBannerList } from './global_banner_list'; export { banners } from './banners';