diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index fd6a9f3b3fa5402..5b22f5aeb2f1dcf 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -69,6 +69,7 @@ export interface AppBase { /** * Hide the UI chrome when the application is mounted. Defaults to `false`. + * Takes precedence over chrome service visibility settings. */ chromeless?: boolean; } diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 08eff2b0ab49c86..e4424da95de3cb0 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { BehaviorSubject, Observable, ReplaySubject, combineLatest, of, merge } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; -import * as Url from 'url'; +import { parse } from 'url'; import { i18n } from '@kbn/i18n'; import { IconType, Breadcrumb as EuiBreadcrumb } from '@elastic/eui'; @@ -41,11 +41,6 @@ export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed'; -function isEmbedParamInHash() { - const { query } = Url.parse(String(window.location.hash).slice(1), true); - return Boolean(query.embed); -} - /** @public */ export interface ChromeBadge { text: string; @@ -94,20 +89,22 @@ export class ChromeService { injectedMetadata, notifications, }: StartDeps): Promise { + // Start off the chrome service hidden if "embed" is in the hash query string. + const isEmbedded = 'embed' in parse(location.hash.slice(1), true).query; + /** * These observables allow consumers to toggle the chrome visibility via either: * 1. Using setIsVisible() to trigger the next chromeHidden$ * 2. Setting `chromeless` when registering an application, which will * reset the visibility whenever the next application is mounted - * 3. Having embed=true in the query string + * 3. Having "embed" in the query string */ - const chromeHidden$ = new BehaviorSubject(false); - const forceHidden$ = of(isEmbedParamInHash()); + const chromeHidden$ = new BehaviorSubject(isEmbedded); const appHidden$ = merge( - // Default the app being hidden to the same value as forceHidden$ in case the - // application service has not emitted an app ID yet, since we want to trigger + // Default the app being hidden to the same value initial value as the chrome visibility + // in case the application service has not emitted an app ID yet, since we want to trigger // combineLatest below regardless of having an application value yet. - forceHidden$, + of(isEmbedded), application.currentAppId$.pipe( map( appId => @@ -117,8 +114,8 @@ export class ChromeService { ) ) ); - const isVisible$ = combineLatest(appHidden$, forceHidden$, chromeHidden$).pipe( - map(([appHidden, forceHidden, chromeHidden]) => !(appHidden || forceHidden || chromeHidden)), + const isVisible$ = combineLatest(appHidden$, chromeHidden$).pipe( + map(([appHidden, chromeHidden]) => !(appHidden || chromeHidden)), takeUntil(this.stop$) ); diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/kibana.json b/test/plugin_functional/plugins/core_plugin_chromeless/kibana.json new file mode 100644 index 000000000000000..a8a5616627726d3 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_chromeless/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "core_plugin_chromeless", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["core_plugin_chromeless"], + "server": false, + "ui": true +} diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/package.json b/test/plugin_functional/plugins/core_plugin_chromeless/package.json new file mode 100644 index 000000000000000..eff6c1e1f142a02 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_chromeless/package.json @@ -0,0 +1,17 @@ +{ + "name": "core_plugin_chromeless", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_plugin_chromeless", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/public/application.tsx b/test/plugin_functional/plugins/core_plugin_chromeless/public/application.tsx new file mode 100644 index 000000000000000..556a9ca140715fd --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_chromeless/public/application.tsx @@ -0,0 +1,74 @@ +/* + * 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, unmountComponentAtNode } from 'react-dom'; +import { BrowserRouter as Router, Route } from 'react-router-dom'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; + +import { AppMountContext, AppMountParameters } from 'kibana/public'; + +const Home = () => ( + + + + +

Welcome to Chromeless!

+
+
+
+ + + + +

Chromeless home page section title

+
+
+
+ Where did all the chrome go? +
+
+); + +const ChromelessApp = ({ basename }: { basename: string; context: AppMountContext }) => ( + + + + + +); + +export const renderApp = ( + context: AppMountContext, + { appBasePath, element }: AppMountParameters +) => { + render(, element); + + return () => unmountComponentAtNode(element); +}; diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/public/index.ts b/test/plugin_functional/plugins/core_plugin_chromeless/public/index.ts new file mode 100644 index 000000000000000..6e9959ecbdf9e53 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_chromeless/public/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { PluginInitializer } from 'kibana/public'; +import { + CorePluginChromelessPlugin, + CorePluginChromelessPluginSetup, + CorePluginChromelessPluginStart, +} from './plugin'; + +export const plugin: PluginInitializer< + CorePluginChromelessPluginSetup, + CorePluginChromelessPluginStart +> = () => new CorePluginChromelessPlugin(); diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx new file mode 100644 index 000000000000000..03870410fb33426 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_chromeless/public/plugin.tsx @@ -0,0 +1,47 @@ +/* + * 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 { Plugin, CoreSetup } from 'kibana/public'; + +export class CorePluginChromelessPlugin + implements Plugin { + public setup(core: CoreSetup, deps: {}) { + core.application.register({ + id: 'chromeless', + title: 'Chromeless', + chromeless: true, + async mount(context, params) { + const { renderApp } = await import('./application'); + return renderApp(context, params); + }, + }); + + return { + getGreeting() { + return 'Hello from Plugin Chromeless!'; + }, + }; + } + + public start() {} + public stop() {} +} + +export type CorePluginChromelessPluginSetup = ReturnType; +export type CorePluginChromelessPluginStart = ReturnType; diff --git a/test/plugin_functional/plugins/core_plugin_chromeless/tsconfig.json b/test/plugin_functional/plugins/core_plugin_chromeless/tsconfig.json new file mode 100644 index 000000000000000..5fcaeafbb0d852a --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_chromeless/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index eec2ec019a51502..138e20b9877610e 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -91,6 +91,18 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider await testSubjects.existOrFail('fooAppPageA'); }); + it('navigating to chromeless application hides chrome', async () => { + await appsMenu.clickLink('Chromeless'); + await loadingScreenNotShown(); + expect(await testSubjects.exists('headerGlobalNav')).to.be(false); + }); + + it('navigating away from chromeless application shows chrome', async () => { + await browser.goBack(); + await loadingScreenNotShown(); + expect(await testSubjects.exists('headerGlobalNav')).to.be(true); + }); + it('can navigate from NP apps to legacy apps', async () => { await appsMenu.clickLink('Management'); await loadingScreenShown();