diff --git a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js index c02309f3ae7572..f0271e30f9bb07 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import path from 'path'; import moment from 'moment'; import 'moment-timezone'; @@ -61,6 +62,11 @@ jest.mock( } ); +// This element uses a `ref` and cannot be rendered by Jest snapshots. +import { RenderedElement } from '../external_runtime/components/rendered_element'; +jest.mock('../external_runtime/components/rendered_element'); +RenderedElement.mockImplementation(() => 'RenderedElement'); + addSerializer(styleSheetSerializer); // Initialize Storyshots and build the Jest Snapshots diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js index 6d28d9d97b23f8..f7c41757ee89f2 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js @@ -57,6 +57,46 @@ module.exports = async ({ config }) => { ], }); + config.module.rules.push({ + test: /\.scss$/, + exclude: /\.module.(s(a|c)ss)$/, + use: [ + { loader: 'style-loader' }, + { loader: 'css-loader', options: { importLoaders: 2 } }, + { + loader: 'postcss-loader', + options: { + path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + { loader: 'sass-loader' }, + ], + }); + + config.module.rules.push({ + test: /\.module\.s(a|c)ss$/, + loader: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 2, + modules: true, + localIdentName: '[name]__[local]___[hash:base64:5]', + }, + }, + { + loader: 'postcss-loader', + options: { + path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + { + loader: 'sass-loader', + }, + ], + }); + // Reference the built DLL file of static(ish) dependencies, which are removed // during kbn:bootstrap and rebuilt if missing. config.plugins.push( @@ -109,8 +149,8 @@ module.exports = async ({ config }) => { }) ); - // Tell Webpack about the ts/x extensions - config.resolve.extensions.push('.ts', '.tsx'); + // Tell Webpack about the extensions + config.resolve.extensions.push('.ts', '.tsx', '.scss'); // Alias imports to either a mock or the proper module or directory. // NOTE: order is important here - `ui/notify` will override `ui/notify/foo` if it diff --git a/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx b/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx index 23fc23195a4746..a6a8ebc96ba6a2 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx @@ -74,7 +74,7 @@ const updateArea = async (area: Element) => { height = workpad.height * (width / workpad.width); } - const options = { + const stage = { height: height || workpad.height, width: width || workpad.width, page: page ? page : workpad.page, @@ -83,7 +83,7 @@ const updateArea = async (area: Element) => { area.classList.add('kbnCanvas'); area.removeAttribute(EMBED); - render(, area); + render(, area); } } }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/footer.components.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/footer.components.examples.storyshot new file mode 100644 index 00000000000000..f32259cc4bdb37 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/footer.components.examples.storyshot @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Footer/components PageControls 1`] = ` +
+
+ +
+
+ +
+
+ +
+
+`; + +exports[`Storyshots runtime/Footer/components Title 1`] = ` +
+
+
+ +
+
+
+
+
+ This is a test title. +
+
+
+
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/footer.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/footer.examples.storyshot new file mode 100644 index 00000000000000..14e32b6bb1e3c2 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/footer.examples.storyshot @@ -0,0 +1,496 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Footer Footer 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ My Canvas Workpad +
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime/Footer PageControls 1`] = ` +
+
+
+ +
+
+ +
+
+ +
+
+
+`; + +exports[`Storyshots runtime/Footer PagePreview 1`] = ` +
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime/Footer Scrubber 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime/Footer Title 1`] = ` +
+
+
+ +
+
+
+
+
+ My Canvas Workpad +
+
+
+
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/runtime.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/runtime.examples.storyshot new file mode 100644 index 00000000000000..0765538eb679fe --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/runtime.examples.storyshot @@ -0,0 +1,322 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime Canvas 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ My Canvas Workpad +
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots runtime Page 1`] = ` +
+
+
+`; + +exports[`Storyshots runtime RenderedElement 1`] = ` +
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/settings.components.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/settings.components.examples.storyshot new file mode 100644 index 00000000000000..8fb600f1074486 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/settings.components.examples.storyshot @@ -0,0 +1,445 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Settings/components AutoplaySettings, autoplay disabled 1`] = ` +
+
+ + + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ Use shorthand notation, like 30s, 10m, or 1h +
+
+
+
+
+
+ +
+ +
+
+
+
+
+`; + +exports[`Storyshots runtime/Settings/components AutoplaySettings, autoplay enabled 1`] = ` +
+
+ + + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ Use shorthand notation, like 30s, 10m, or 1h +
+
+
+
+
+
+ +
+ +
+
+
+
+
+`; + +exports[`Storyshots runtime/Settings/components ToolbarSettings, autohide disabled 1`] = ` +
+
+
+ + + + + + + + + +
+
+ Hide the toolbar when the mouse is not within the Canvas? +
+
+
+`; + +exports[`Storyshots runtime/Settings/components ToolbarSettings, autohide enabled 1`] = ` +
+
+
+ + + + + + + + + +
+
+ Hide the toolbar when the mouse is not within the Canvas? +
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/settings.examples.storyshot b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/settings.examples.storyshot new file mode 100644 index 00000000000000..2623c225abf094 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/__snapshots__/settings.examples.storyshot @@ -0,0 +1,305 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots runtime/Settings AutoplaySettings 1`] = ` +
+
+
+ + + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+ Use shorthand notation, like 30s, 10m, or 1h +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+`; + +exports[`Storyshots runtime/Settings Settings 1`] = ` +
+
+
+
+
+ +
+
+
+
+
+`; + +exports[`Storyshots runtime/Settings ToolbarSettings 1`] = ` +
+
+
+
+ + + + + + + + + +
+
+ Hide the toolbar when the mouse is not within the Canvas? +
+
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/footer.components.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/footer.components.examples.tsx new file mode 100644 index 00000000000000..409c21a9a45617 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/footer.components.examples.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { Title } from '../footer/title'; +import { PageControls } from '../footer/page_controls'; + +storiesOf('runtime/Footer/components', module) + .add('Title', () => ( +
+ + </div> + )) + .add('PageControls', () => ( + <PageControls + page={0} + totalPages={1} + onSetPageNumber={action('onSetPageNumber')} + onToggleScrubber={action('onToggleScrubber')} + /> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/footer.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/footer.examples.tsx new file mode 100644 index 00000000000000..7d26b0a5d05e9e --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/footer.examples.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { Context } from '../../context/mock'; + +import { Footer } from '../footer/footer.container'; +import { Title } from '../footer/title.container'; +import { PageControls } from '../footer/page_controls.container'; +import { PagePreview } from '../footer/page_preview.container'; +import { Scrubber } from '../footer/scrubber.container'; + +storiesOf('runtime/Footer', module) + .add('Footer', () => ( + <Context height={172}> + <Footer /> + </Context> + )) + .add('Title', () => ( + <Context style={{ background: '#333', padding: 10 }}> + <Title /> + </Context> + )) + .add('Scrubber', () => ( + <Context height={172} isScrubberVisible={true}> + <Scrubber /> + </Context> + )) + .add('PageControls', () => ( + <Context style={{ background: '#333', padding: 10 }}> + <PageControls /> + </Context> + )) + .add('PagePreview', () => ( + <Context> + <PagePreview height={172} index={0} /> + </Context> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/runtime.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/runtime.examples.tsx new file mode 100644 index 00000000000000..a66e85c9afa706 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/runtime.examples.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { Context } from '../../context/mock'; + +import hello from '../../test/hello.json'; +import { Canvas } from '../canvas.container'; +import { Page } from '../page.container'; +import { RenderedElement } from '../rendered_element.container'; + +storiesOf('runtime', module) + .add('Canvas', () => ( + <Context height={448}> + <Canvas /> + </Context> + )) + .add('Page', () => ( + <Context height={720}> + <Page index={0} /> + </Context> + )) + .add('RenderedElement', () => ( + <Context height={720}> + <RenderedElement element={hello.pages[0].elements[0]} index={0} /> + </Context> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/settings.components.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/settings.components.examples.tsx new file mode 100644 index 00000000000000..6c0c337cdcd53a --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/settings.components.examples.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { AutoplaySettings } from '../footer/settings/autoplay_settings'; +import { ToolbarSettings } from '../footer/settings/toolbar_settings'; + +storiesOf('runtime/Settings/components', module) + .add('AutoplaySettings, autoplay disabled', () => ( + <AutoplaySettings + onSetAutoplay={action('onSetAutoplay')} + isEnabled={false} + interval="5s" + onSetInterval={action('onSetInterval')} + /> + )) + .add('AutoplaySettings, autoplay enabled', () => ( + <AutoplaySettings + onSetAutoplay={action('onSetAutoplay')} + isEnabled={true} + interval="5s" + onSetInterval={action('onSetInterval')} + /> + )) + .add('ToolbarSettings, autohide enabled', () => ( + <ToolbarSettings onSetAutohide={action('onSetInterval')} isAutohide={true} /> + )) + .add('ToolbarSettings, autohide disabled', () => ( + <ToolbarSettings onSetAutohide={action('onSetInterval')} isAutohide={false} /> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/settings.examples.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/settings.examples.tsx new file mode 100644 index 00000000000000..c2f481007fa7c8 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/__examples__/settings.examples.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { Context } from '../../context/mock'; + +import { AutoplaySettings } from '../footer/settings/autoplay_settings.container'; +import { Settings } from '../footer/settings/settings.container'; +import { ToolbarSettings } from '../footer/settings/toolbar_settings.container'; + +storiesOf('runtime/Settings', module) + .add('Settings', () => ( + <Context style={{ background: '#333', padding: 10 }}> + <Settings /> + </Context> + )) + .add('AutoplaySettings', () => ( + <Context width={250} style={{ background: '#fff' }}> + <AutoplaySettings /> + </Context> + )) + .add('ToolbarSettings', () => ( + <Context width={250} style={{ background: '#fff' }}> + <ToolbarSettings /> + </Context> + )); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx index 936349155ff90a..55edc4217d5df4 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx @@ -5,22 +5,15 @@ */ import React from 'react'; -// @ts-ignore Untyped package -import { RenderFunctionsRegistry } from 'data/interpreter'; -import { Canvas } from './canvas'; -import { - initialExternalEmbedState, - ExternalEmbedStateProvider, - ExternalEmbedState, -} from '../context'; +import { Canvas } from './canvas.container'; +import { initialExternalEmbedState, ExternalEmbedStateProvider } from '../context'; // @ts-ignore Untyped local import { renderFunctions } from '../../canvas_plugin_src/renderers'; -import { CanvasRenderedWorkpad } from '../types'; +import { CanvasRenderedWorkpad, ExternalEmbedState, Stage } from '../types'; +import { RendererFactory, RendererSpec } from '../../types'; interface Props { - height: number; - width: number; - page: number; + stage: Stage; workpad: CanvasRenderedWorkpad; } @@ -28,23 +21,20 @@ interface Props { * The overall Embedded Workpad app; the highest-layer component. */ export const App = (props: Props) => { - const { workpad, page, height, width } = props; + const { workpad, stage } = props; - // Register all of the rendering experessions with a bespoke registry. - const renderersRegistry = new RenderFunctionsRegistry(); - - renderFunctions.forEach((fn: Function | undefined) => { + const renderers: { [key: string]: RendererSpec } = {}; + renderFunctions.forEach((fn: RendererFactory | undefined) => { if (fn) { - renderersRegistry.register(fn); + const func = fn(); + renderers[func.name] = func; } }); const initialState: ExternalEmbedState = { ...initialExternalEmbedState, - height, - page, - renderersRegistry, - width, + stage, + renderers, workpad, }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.container.tsx new file mode 100644 index 00000000000000..b15b91cb09b369 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.container.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState, setPageAction, setScrubberVisibleAction } from '../context'; +import { Canvas as CanvasComponent, onSetPageProp, onSetScrubberVisibleProp } from './canvas'; + +export const Canvas = () => { + const [{ workpad, stage, settings, refs }, dispatch] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const onSetPage: onSetPageProp = (page: number) => { + dispatch(setPageAction(page)); + }; + + const onSetScrubberVisible: onSetScrubberVisibleProp = (visible: boolean) => { + dispatch(setScrubberVisibleAction(visible)); + }; + + return ( + <CanvasComponent + {...{ + onSetPage, + onSetScrubberVisible, + refs, + settings, + stage, + workpad, + }} + /> + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx index 7a999157f2fd9e..d7d3b4e6bab341 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx @@ -5,42 +5,52 @@ */ import React, { useState } from 'react'; -import { useExternalEmbedState, setPage, setScrubberVisible } from '../context'; -import { Page } from './page'; +import { Page } from './page.container'; import { Footer, FOOTER_HEIGHT } from './footer'; import { getTimeInterval } from '../../public/lib/time_interval'; -// @ts-ignore CSS Module -import css from './canvas.module'; +import css from './canvas.module.scss'; +import { CanvasRenderedWorkpad, Stage, Settings, Refs } from '../types'; let timeout: number = 0; +export type onSetPageProp = (page: number) => void; +export type onSetScrubberVisibleProp = (visible: boolean) => void; + +interface Props { + onSetPage: onSetPageProp; + onSetScrubberVisible: onSetScrubberVisibleProp; + refs: Pick<Refs, 'stage'>; + settings: Settings; + stage: Stage; + workpad: Pick<CanvasRenderedWorkpad, 'height' | 'width' | 'pages'>; +} + /** * The "stage" for a workpad, which composes the toolbar and other components. */ -export const Canvas = () => { - const [ - { workpad, height: containerHeight, width: containerWidth, page, settings, refs }, - dispatch, - ] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - +export const Canvas = ({ + onSetPage = () => {}, + onSetScrubberVisible = () => {}, + refs, + settings, + stage, + workpad, +}: Props) => { const { toolbar, autoplay } = settings; - const { height, width, pages } = workpad; - const ratio = Math.max(width / containerWidth, height / containerHeight); - const transform = `scale3d(${containerHeight / (containerHeight * ratio)}, ${containerWidth / - (containerWidth * ratio)}, 1)`; + const { height: stageHeight, width: stageWidth, page } = stage; + const { height: workpadHeight, width: workpadWidth } = workpad; + const ratio = Math.max(workpadWidth / stageWidth, workpadHeight / stageHeight); + const transform = `scale3d(${stageHeight / (stageHeight * ratio)}, ${stageWidth / + (stageWidth * ratio)}, 1)`; const pageStyle = { - height, + height: workpadHeight, transform, - width, + width: workpadWidth, }; - if (autoplay.enabled && autoplay.interval) { + if (autoplay.isEnabled && autoplay.interval) { // We need to clear the timeout every time, even if it doesn't need to be or // it's null. Since one could select a different page from the scrubber at // any point, or change the interval, we need to make sure the interval is @@ -49,19 +59,19 @@ export const Canvas = () => { clearTimeout(timeout); timeout = setTimeout( - () => dispatch(setPage(page >= workpad.pages.length - 1 ? 0 : page + 1)), + () => onSetPage(page >= workpad.pages.length - 1 ? 0 : page + 1), getTimeInterval(autoplay.interval) ); } - const [toolbarHidden, setToolbarHidden] = useState(toolbar.autohide); - const rootHeight = containerHeight + (toolbar.autohide ? 0 : FOOTER_HEIGHT); + const [toolbarHidden, setToolbarHidden] = useState(toolbar.isAutohide); + const rootHeight = stageHeight + (toolbar.isAutohide ? 0 : FOOTER_HEIGHT); const hideToolbar = (hidden: boolean) => { - if (settings.toolbar.autohide) { + if (toolbar.isAutohide) { if (hidden) { // Hide the scrubber if we hide the toolbar. - dispatch(setScrubberVisible(false)); + onSetScrubberVisible(false); } setToolbarHidden(hidden); } @@ -70,17 +80,17 @@ export const Canvas = () => { return ( <div className={css.root} - style={{ height: rootHeight, width: containerWidth }} + style={{ height: rootHeight, width: stageWidth }} onMouseEnter={() => hideToolbar(false)} onMouseLeave={() => hideToolbar(true)} ref={refs.stage} > - <div className={css.container} style={{ height: containerHeight, width: containerWidth }}> + <div className={css.container} style={{ height: stageHeight, width: stageWidth }}> <div className={css.page} style={pageStyle}> - <Page page={pages[page]} /> + <Page index={page} /> </div> </div> - <Footer hidden={toolbarHidden} /> + <Footer isHidden={toolbarHidden} /> </div> ); }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.container.tsx new file mode 100644 index 00000000000000..9fe43496092ff9 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.container.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState, setScrubberVisibleAction } from '../../context'; +import { Footer as FooterComponent } from './footer'; +export { FOOTER_HEIGHT } from './footer'; + +interface Props { + isHidden?: boolean; +} + +/** + * The footer of the Embedded Workpad. + */ +export const Footer = ({ isHidden = false }: Props) => { + const [{ workpad, settings, footer }, dispatch] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { toolbar } = settings; + const { isAutohide } = toolbar; + const { isScrubberVisible } = footer; + + // If autohide is enabled, and the toolbar is hidden, set the scrubber + // visibility to hidden. This is useful for state changes where one + // sets the footer to hidden, and the scrubber would be left open with + // no toolbar. + if (isAutohide && isHidden && isScrubberVisible) { + dispatch(setScrubberVisibleAction(false)); + } + + return <FooterComponent {...{ isHidden, isAutohide }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx index 6716d46fba52ad..04ca1a270fdcfe 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx @@ -6,46 +6,32 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useExternalEmbedState, setScrubberVisible } from '../../context'; -import { Scrubber } from './scrubber'; -import { Title } from './title'; -import { PageControls } from './page_controls'; +import { Scrubber } from './scrubber.container'; +import { Title } from './title.container'; +import { PageControls } from './page_controls.container'; import { Settings } from './settings'; -// @ts-ignore CSS Module -import css from './footer.module'; +import css from './footer.module.scss'; export const FOOTER_HEIGHT = 48; interface Props { - hidden?: boolean; + isAutohide?: boolean; + isHidden?: boolean; } /** * The footer of the Embedded Workpad. */ -export const Footer = ({ hidden = false }: Props) => { - const [{ workpad, settings }] = useExternalEmbedState(); - if (!workpad) { - return null; - } - - const { autohide } = settings.toolbar; - - // If autohide is enabled, and the toolbar is hidden, set the scrubber - // visibility to hidden. This is useful for state changes where one - // sets the footer to hidden, and the scrubber would be left open with - // no toolbar. - if (autohide && hidden) { - setScrubberVisible(false); - } +export const Footer = ({ isAutohide = false, isHidden = false }: Props) => { + const { root, bar, title } = css; return ( - <div className={css.root} style={{ height: FOOTER_HEIGHT }}> + <div className={root} style={{ height: FOOTER_HEIGHT }}> <Scrubber /> - <div className={css.bar} style={{ bottom: autohide && hidden ? -FOOTER_HEIGHT : 0 }}> + <div className={bar} style={{ bottom: isAutohide && isHidden ? -FOOTER_HEIGHT : 0 }}> <EuiFlexGroup gutterSize="none"> - <EuiFlexItem className={css.title}> + <EuiFlexItem className={title}> <Title /> </EuiFlexItem> <EuiFlexItem grow={false}> diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts index 72acba529b25b7..42fc2a38e59096 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './footer'; +export * from './footer.container'; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.container.tsx new file mode 100644 index 00000000000000..e7aaafd3947021 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.container.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + useExternalEmbedState, + setScrubberVisibleAction, + setPageAction, + setAutoplayAction, +} from '../../context'; +import { PageControls as PageControlsComponent } from './page_controls'; + +/** + * The page count and paging controls within the footer of the Embedded Workpad. + */ +export const PageControls = () => { + const [{ workpad, footer, stage }, dispatch] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { isScrubberVisible } = footer; + const { page } = stage; + const totalPages = workpad.pages.length; + + const onToggleScrubber = () => { + dispatch(setAutoplayAction(false)); + dispatch(setScrubberVisibleAction(!isScrubberVisible)); + }; + const onSetPageNumber = (number: number) => dispatch(setPageAction(number)); + + return <PageControlsComponent {...{ onToggleScrubber, onSetPageNumber, page, totalPages }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx index 86075c274740d6..b1c993f515adc5 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx @@ -6,42 +6,36 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiButtonEmpty, EuiText } from '@elastic/eui'; -import { useExternalEmbedState, setScrubberVisible, setPage, setAutoplay } from '../../context'; + +export type onSetPageNumberProp = (page: number) => void; +export type onToggleScrubberProp = () => void; + +interface Props { + page: number; + totalPages: number; + onSetPageNumber: onSetPageNumberProp; + onToggleScrubber: onToggleScrubberProp; +} /** * The page count and paging controls within the footer of the Embedded Workpad. */ -export const PageControls = () => { - const [{ workpad, footer, page }, dispatch] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - - const { isScrubberVisible } = footer; - - const toggleScrubber = () => { - dispatch(setAutoplay(false)); - dispatch(setScrubberVisible(!isScrubberVisible)); - }; - - const setPageNumber = (number: number) => dispatch(setPage(number)); +export const PageControls = ({ onSetPageNumber, page, totalPages, onToggleScrubber }: Props) => { const currentPage = page + 1; - const totalPages = workpad.pages.length; return ( <EuiFlexGroup alignItems="center" gutterSize="none" style={{ margin: '0 12px' }}> <EuiFlexItem grow={false}> <EuiButtonIcon color="ghost" - onClick={() => setPageNumber(page - 1)} + onClick={() => onSetPageNumber(page - 1)} iconType="arrowLeft" disabled={currentPage <= 1} aria-label="Previous Page" /> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonEmpty color="ghost" size="s" onClick={toggleScrubber}> + <EuiButtonEmpty color="ghost" size="s" onClick={onToggleScrubber}> <EuiText color="ghost" size="s"> Page {currentPage} {totalPages > 1 ? ` of ${totalPages}` : null} @@ -51,7 +45,7 @@ export const PageControls = () => { <EuiFlexItem grow={false}> <EuiButtonIcon color="ghost" - onClick={() => setPageNumber(page + 1)} + onClick={() => onSetPageNumber(page + 1)} iconType="arrowRight" disabled={currentPage >= totalPages} aria-label="Next Page" diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.container.tsx new file mode 100644 index 00000000000000..41495f330b4f39 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.container.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../context'; +import { setPageAction } from '../../context/actions'; +import { PagePreview as PagePreviewComponent } from './page_preview'; + +interface Props { + index: number; + height: number; +} + +/** + * The small preview of the page shown within the `Scrubber`. + */ +export const PagePreview = ({ index, height }: Props) => { + const [{ workpad }, dispatch] = useExternalEmbedState(); + if (!workpad) { + return null; + } + + const page = workpad.pages[index]; + const onClick = (pageIndex: number) => dispatch(setPageAction(pageIndex)); + const { height: workpadHeight, width: workpadWidth } = workpad; + + return ( + <PagePreviewComponent {...{ onClick, height, workpadHeight, workpadWidth, page, index }} /> + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx index 805d68a9e2f2c5..213cc94a865d12 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx @@ -5,36 +5,39 @@ */ import React from 'react'; -import { useExternalEmbedState } from '../../context'; import { Page } from '../page'; -import { setPage } from '../../context/actions'; import { CanvasRenderedPage } from '../../types'; -// @ts-ignore CSS Module -import css from './page_preview.module'; +import css from './page_preview.module.scss'; -interface Props { - number: number; +export type onClickProp = (index: number) => void; + +export interface Props { height: number; + index: number; + onClick: onClickProp; page: CanvasRenderedPage; + workpadHeight: number; + workpadWidth: number; } /** * The small preview of the page shown within the `Scrubber`. */ -export const PagePreview = ({ number, page, height }: Props) => { - const [{ workpad }, dispatch] = useExternalEmbedState(); - if (!workpad) { - return null; - } - - const onClick = (index: number) => dispatch(setPage(index)); - const { height: workpadHeight, width: workpadWidth } = workpad; +export const PagePreview = ({ + height, + index, + onClick, + page, + workpadHeight, + workpadWidth, +}: Props) => { const scale = height / workpadHeight; const style = { height: workpadHeight * scale, width: workpadWidth * scale, }; + const transform = { ...style, transform: `scale3d(${scale}, ${scale}, 1)`, @@ -43,12 +46,12 @@ export const PagePreview = ({ number, page, height }: Props) => { return ( <div className={css.root} - onClick={() => onClick(number)} - onKeyPress={() => onClick(number)} + onClick={() => onClick(index)} + onKeyPress={() => onClick(index)} style={style} > <div className={css.preview} style={transform}> - <Page page={page} /> + <Page {...{ page }} height={workpadHeight} width={workpadWidth} /> </div> </div> ); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.container.tsx new file mode 100644 index 00000000000000..929a732040e0ed --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.container.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../context'; +import { Scrubber as ScrubberComponent } from './scrubber'; + +/** + * The panel of previews of the pages in the workpad, allowing one to select and + * navigate to a specific page. + */ +export const Scrubber = () => { + const [{ workpad, footer }] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { pages } = workpad; + const { isScrubberVisible } = footer; + + return <ScrubberComponent {...{ pages, isScrubberVisible }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx index 140b45fc171cc9..f5f0a8b43b143a 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx @@ -6,11 +6,15 @@ import React from 'react'; import classnames from 'classnames'; -import { useExternalEmbedState } from '../../context'; -import { PagePreview } from './page_preview'; +import { PagePreview } from './page_preview.container'; -// @ts-ignore CSS Module -import css from './scrubber.module'; +import css from './scrubber.module.scss'; +import { CanvasRenderedPage } from '../../types'; + +interface Props { + isScrubberVisible: boolean; + pages: CanvasRenderedPage[]; +} const THUMBNAIL_HEIGHT = 100; @@ -18,19 +22,11 @@ const THUMBNAIL_HEIGHT = 100; * The panel of previews of the pages in the workpad, allowing one to select and * navigate to a specific page. */ -export const Scrubber = () => { - const [{ workpad, footer }] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - - const { pages } = workpad; - const { isScrubberVisible } = footer; +export const Scrubber = ({ isScrubberVisible, pages }: Props) => { const className = isScrubberVisible ? classnames(css.root, css.visible) : css.root; const slides = pages.map((page, index) => ( - <PagePreview key={page.id} page={page} number={index} height={THUMBNAIL_HEIGHT} /> + <PagePreview key={page.id} height={THUMBNAIL_HEIGHT} {...{ index }} /> )); return ( diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.container.tsx new file mode 100644 index 00000000000000..e26fc5e27b1370 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.container.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + useExternalEmbedState, + setAutoplayAction, + setAutoplayIntervalAction, +} from '../../../context'; + +import { + AutoplaySettings as AutoplaySettingsComponent, + onSetAutoplayProp, + onSetIntervalProp, +} from './autoplay_settings'; + +/** + * The panel used to configure Autolay in Embedded Workpads. + */ +export const AutoplaySettings = () => { + const [{ settings }, dispatch] = useExternalEmbedState(); + + const { autoplay } = settings; + const { isEnabled, interval } = autoplay; + + const onSetInterval: onSetIntervalProp = (newInterval: string) => + dispatch(setAutoplayIntervalAction(newInterval)); + + const onSetAutoplay: onSetAutoplayProp = (enabled: boolean) => + dispatch(setAutoplayAction(enabled)); + + return <AutoplaySettingsComponent {...{ isEnabled, interval, onSetAutoplay, onSetInterval }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx index d37bdaebefa51e..f9543e85f32a77 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx @@ -6,33 +6,41 @@ import React from 'react'; import { EuiHorizontalRule, EuiSwitch } from '@elastic/eui'; -import { useExternalEmbedState, setAutoplay, setAutoplayInterval } from '../../../context'; import { createTimeInterval } from '../../../../public/lib/time_interval'; // @ts-ignore Untyped local import { CustomInterval } from '../../../../public/components/workpad_header/control_settings/custom_interval'; +export type onSetAutoplayProp = (autoplay: boolean) => void; +export type onSetIntervalProp = (interval: string) => void; + +export interface Props { + isEnabled: boolean; + interval: string; + onSetAutoplay?: onSetAutoplayProp; + onSetInterval?: onSetIntervalProp; +} + /** * The panel used to configure Autolay in Embedded Workpads. */ -export const AutoplaySettings = () => { - const [{ settings }, dispatch] = useExternalEmbedState(); - - const { autoplay } = settings; - - return ( - <div style={{ padding: 16 }}> - <EuiSwitch - name="cycle" - id="cycle" - label="Cycle Slides" - checked={autoplay.enabled} - onChange={() => dispatch(setAutoplay(!autoplay.enabled))} - /> - <EuiHorizontalRule margin="m" /> - <CustomInterval - defaultValue={autoplay.interval} - onSubmit={(value: number) => dispatch(setAutoplayInterval(createTimeInterval(value)))} - /> - </div> - ); -}; +export const AutoplaySettings = ({ + isEnabled, + interval, + onSetAutoplay = () => {}, + onSetInterval = () => {}, +}: Props) => ( + <div style={{ padding: 16 }}> + <EuiSwitch + name="cycle" + id="cycle" + label="Cycle Slides" + checked={isEnabled} + onChange={() => onSetAutoplay(!isEnabled)} + /> + <EuiHorizontalRule margin="m" /> + <CustomInterval + defaultValue={interval} + onSubmit={(value: number) => onSetInterval(createTimeInterval(value))} + /> + </div> +); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts index 87d5cf67e69652..0202e3d76e099a 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './settings'; +export * from './settings.container'; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.container.tsx new file mode 100644 index 00000000000000..199defd464ba32 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.container.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../../context'; +import { Settings as SettingsComponent } from './settings'; +/** + * The Settings Popover for External Workpads. + */ +export const Settings = () => { + const [{ refs }] = useExternalEmbedState(); + + return <SettingsComponent refs={refs} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx index a4dad62b25bc14..ce1b8e64af0ccc 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx @@ -5,26 +5,25 @@ */ import React, { useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiPopover, EuiContextMenu } from '@elastic/eui'; -import { useExternalEmbedState } from '../../../context'; -// @ts-ignore Untyped local -import { CustomInterval } from '../../../../public/components/workpad_header/control_settings/custom_interval'; -import { ToolbarSettings } from './toolbar_settings'; -import { AutoplaySettings } from './autoplay_settings'; - -// @ts-ignore CSS Module -import css from './settings.module'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonIcon, + EuiPopover, + EuiPopoverProps, + EuiContextMenu, +} from '@elastic/eui'; +import { Refs } from '../../../types'; +import { ToolbarSettings } from './toolbar_settings.container'; +import { AutoplaySettings } from './autoplay_settings.container'; +interface Props { + refs: Refs; +} /** * The Settings Popover for External Workpads. */ -export const Settings = () => { - const [{ workpad, refs }] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - +export const Settings = ({ refs }: Props) => { const [isPopoverOpen, setPopoverOpen] = useState(false); const button = ( <EuiButtonIcon @@ -69,27 +68,32 @@ export const Settings = () => { panel: { id: 2, title: 'Toolbar', - content: <ToolbarSettings onChange={() => setPopoverOpen(false)} />, + content: <ToolbarSettings onSetAutohide={() => setPopoverOpen(false)} />, }, }, ], }); + const props: EuiPopoverProps = { + closePopover: () => setPopoverOpen(false), + isOpen: isPopoverOpen, + button, + panelPaddingSize: 'none', + withTitle: true, + anchorPosition: 'upRight', + }; + + if (refs.stage.current) { + props.insert = { + sibling: refs.stage.current, + position: 'after', + }; + } + return ( <EuiFlexGroup alignItems="flexEnd" justifyContent="center" direction="column" gutterSize="none"> <EuiFlexItem grow={false}> - {/* - //@ts-ignore EuiPopover missing insert property */} - <EuiPopover - closePopover={() => setPopoverOpen(false)} - id="settings" - isOpen={isPopoverOpen} - button={button} - panelPaddingSize="none" - withTitle - anchorPosition="upRight" - insert={{ sibling: refs.stage.current, position: 'after' }} - > + <EuiPopover id="settings" {...props}> <EuiContextMenu initialPanelId={0} panels={panels} /> </EuiPopover> </EuiFlexItem> diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.container.tsx new file mode 100644 index 00000000000000..af876e87088050 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.container.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState, setToolbarAutohideAction } from '../../../context'; +import { ToolbarSettings as ToolbarSettingsComponent, onSetAutohideProp } from './toolbar_settings'; + +interface Props { + onSetAutohide?: onSetAutohideProp; +} + +/** + * The settings panel for the Toolbar of an Embedded Workpad. + */ +export const ToolbarSettings = ({ onSetAutohide = () => {} }: Props) => { + const [{ settings }, dispatch] = useExternalEmbedState(); + + const { toolbar } = settings; + const { isAutohide } = toolbar; + + const onSetAutohideFn: onSetAutohideProp = (autohide: boolean) => { + onSetAutohide(autohide); + dispatch(setToolbarAutohideAction(autohide)); + }; + + return <ToolbarSettingsComponent onSetAutohide={onSetAutohideFn} {...{ isAutohide }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx index 8e11e8a075acc4..bd830cb15e6edb 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx @@ -6,23 +6,18 @@ import React from 'react'; import { EuiSwitch, EuiFormRow } from '@elastic/eui'; -import { useExternalEmbedState, setToolbarAutohide } from '../../../context'; -// @ts-ignore CSS Module -import css from './settings.module'; +export type onSetAutohideProp = (isAutohide: boolean) => void; interface Props { - onChange?: () => void; + isAutohide: boolean; + onSetAutohide?: onSetAutohideProp; } /** * The settings panel for the Toolbar of an Embedded Workpad. */ -export const ToolbarSettings = ({ onChange = () => {} }: Props) => { - const [{ settings }, dispatch] = useExternalEmbedState(); - - const { toolbar } = settings; - +export const ToolbarSettings = ({ isAutohide, onSetAutohide = () => {} }: Props) => { return ( <div style={{ padding: 16 }}> <EuiFormRow helpText="Hide the toolbar when the mouse is not within the Canvas?"> @@ -30,11 +25,8 @@ export const ToolbarSettings = ({ onChange = () => {} }: Props) => { name="toolbarHide" id="toolbarHide" label="Hide Toolbar" - checked={toolbar.autohide} - onChange={() => { - onChange(); - dispatch(setToolbarAutohide(!toolbar.autohide)); - }} + checked={isAutohide} + onChange={() => onSetAutohide(!isAutohide)} /> </EuiFormRow> </div> diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.container.tsx new file mode 100644 index 00000000000000..96740d2e679788 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.container.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../../context'; +import { Title as TitleComponent } from './title'; + +/** + * The title of the workpad displayed in the right-hand of the footer. + */ +export const Title = () => { + const [{ workpad }] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { name: title } = workpad; + + return <TitleComponent {...{ title }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx index 6296e55007ef60..c7339fe14d739f 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx @@ -6,28 +6,22 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { useExternalEmbedState } from '../../context'; +interface Props { + title: string; +} /** * The title of the workpad displayed in the right-hand of the footer. */ -export const Title = () => { - const [{ workpad }] = useExternalEmbedState(); - - if (!workpad) { - return null; - } - - return ( - <EuiFlexGroup gutterSize="s" justifyContent="flexStart" alignItems="center"> - <EuiFlexItem grow={false}> - <EuiIcon type="logoKibana" size="m" /> - </EuiFlexItem> - <EuiFlexItem grow={false} style={{ minWidth: 0 }}> - <EuiText color="ghost" size="s"> - <div className="eui-textTruncate">{workpad.name}</div> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - ); -}; +export const Title = ({ title }: Props) => ( + <EuiFlexGroup gutterSize="s" justifyContent="flexStart" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiIcon type="logoKibana" size="m" /> + </EuiFlexItem> + <EuiFlexItem grow={false} style={{ minWidth: 0, cursor: 'default' }}> + <EuiText color="ghost" size="s"> + <div className="eui-textTruncate">{title}</div> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> +); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/page.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/page.container.tsx new file mode 100644 index 00000000000000..9f10ac3fb543f8 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/page.container.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../context'; +import { Page as PageComponent } from './page'; + +interface Props { + index: number; +} + +export const Page = ({ index }: Props) => { + const [{ workpad }] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { height, width, pages } = workpad; + const page = pages[index]; + + return <PageComponent {...{ page, height, width }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx index 538bf885c566d3..25bf07e5b8329a 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx @@ -5,28 +5,22 @@ */ import React from 'react'; -import { RenderedElement } from './rendered_element'; -import { useExternalEmbedState } from '../context'; +import { RenderedElement } from './rendered_element.container'; import { CanvasRenderedPage, CanvasRenderedElement } from '../types'; -// @ts-ignore CSS Module -import css from './page.module'; +import css from './page.module.scss'; interface Props { + height: number; + width: number; page: CanvasRenderedPage; } -export const Page = (props: Props) => { - const [{ workpad }] = useExternalEmbedState(); - if (!workpad) { - return null; - } +export const Page = ({ page, height, width }: Props) => { + const { elements, style, id } = page; - const { height, width, id } = workpad; - const { elements, style } = props.page; - - const output = elements.map((element: CanvasRenderedElement, index) => ( - <RenderedElement key={element.id} element={element} number={index + 1} /> + const output = elements.map((element: CanvasRenderedElement, i) => ( + <RenderedElement key={element.id} element={element} index={i + 1} /> )); return ( diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.container.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.container.tsx new file mode 100644 index 00000000000000..c6ce2230be8caf --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.container.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useExternalEmbedState } from '../context'; +import { CanvasRenderedElement } from '../types'; +import { RenderedElement as RenderedElementComponent } from './rendered_element'; + +interface Props { + element: CanvasRenderedElement; + index: number; +} + +export const RenderedElement = ({ index, element }: Props) => { + const [{ renderers }] = useExternalEmbedState(); + + const { expressionRenderable } = element; + const { value } = expressionRenderable; + const { as } = value; + const fn = renderers[as]; + + return <RenderedElementComponent {...{ element, fn, index }} />; +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx index 2a415d71ec1e1b..ca8659dfb6ca8a 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx @@ -14,12 +14,13 @@ import { elementToShape } from '../../public/components/workpad_page/utils'; import { CanvasRenderedElement } from '../types'; import { ExternalEmbedContext } from '../context'; -// @ts-ignore CSS Module -import css from './rendered_element.module'; +import css from './rendered_element.module.scss'; +import { RendererSpec } from '../../types'; interface Props { element: CanvasRenderedElement; - number?: number; + index: number; + fn: RendererSpec; } /** @@ -38,31 +39,34 @@ export class RenderedElement extends React.PureComponent<Props> { } componentDidMount() { - const [{ renderersRegistry }] = this.context; - const { element } = this.props; + const { element, fn } = this.props; const { expressionRenderable } = element; const { value } = expressionRenderable; const { as } = value; - const fn = renderersRegistry.get(as); + const { current } = this.ref; + + if (!current) { + return; + } try { // TODO: These are stubbed, but may need implementation. - fn.render(this.ref.current, value.value, { + fn.render(current, value.value, { done: () => {}, onDestroy: () => {}, onResize: () => {}, setFilter: () => {}, - getFilter: () => {}, + getFilter: () => '', }); } catch (e) { // eslint-disable-next-line no-console - console.log(e.message); + console.log(as, e.message); } } render() { - const { element, number } = this.props; - const shape = elementToShape(element, number || 1); + const { element, index } = this.props; + const shape = elementToShape(element, index || 1); const { id, expressionRenderable, position } = element; const { value } = expressionRenderable; const { as, css: elementCSS, containerStyle } = value; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts b/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts index 37b1d082dd8f9c..c5a92823e73d18 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts @@ -37,20 +37,21 @@ const createAction = <T extends ExternalEmbedActions, P>( * Set the current `CanvasRenderedWorkpad`. * @param workpad A `CanvasRenderedWorkpad` to display. */ -export const setWorkpad = (workpad: CanvasRenderedWorkpad) => +export const setWorkpadAction = (workpad: CanvasRenderedWorkpad) => createAction(ExternalEmbedActions.SET_WORKPAD, { workpad }); /** * Set the current page to display * @param page The zero-indexed page to display. */ -export const setPage = (page: number) => createAction(ExternalEmbedActions.SET_PAGE, { page }); +export const setPageAction = (page: number) => + createAction(ExternalEmbedActions.SET_PAGE, { page }); /** * Set the visibility of the page scrubber. * @param visible True if it should be visible, false otherwise. */ -export const setScrubberVisible = (visible: boolean) => { +export const setScrubberVisibleAction = (visible: boolean) => { return createAction(ExternalEmbedActions.SET_SCRUBBER_VISIBLE, { visible }); }; @@ -58,22 +59,22 @@ export const setScrubberVisible = (visible: boolean) => { * Set whether the slides should automatically advance. * @param autoplay True if it should automatically advance, false otherwise. */ -export const setAutoplay = (autoplay: boolean) => - createAction(ExternalEmbedActions.SET_AUTOPLAY, { autoplay }); +export const setAutoplayAction = (isEnabled: boolean) => + createAction(ExternalEmbedActions.SET_AUTOPLAY, { isEnabled }); /** * Set whether the slides should animate when advanced. * @param animate True if it should animate when advanced, false otherwise. */ -export const setAutoplayAnimate = (animate: boolean) => - createAction(ExternalEmbedActions.SET_AUTOPLAY_ANIMATE, { animate }); +export const setAutoplayAnimateAction = (isAnimated: boolean) => + createAction(ExternalEmbedActions.SET_AUTOPLAY_ANIMATE, { isAnimated }); /** * Set the interval in which slide will advance. This is a `string` identical to * that used in Canvas proper: `1m`, `2s`, etc. * @param autoplay The interval in which slides should advance. */ -export const setAutoplayInterval = (interval: string) => +export const setAutoplayIntervalAction = (interval: string) => createAction(ExternalEmbedActions.SET_AUTOPLAY_INTERVAL, { interval }); /** @@ -81,17 +82,17 @@ export const setAutoplayInterval = (interval: string) => * embedded workpad. * @param autohide True if the toolbar should hide, false otherwise. */ -export const setToolbarAutohide = (autohide: boolean) => - createAction(ExternalEmbedActions.SET_TOOLBAR_AUTOHIDE, { autohide }); +export const setToolbarAutohideAction = (isAutohide: boolean) => + createAction(ExternalEmbedActions.SET_TOOLBAR_AUTOHIDE, { isAutohide }); const actions = { - setWorkpad, - setPage, - setScrubberVisible, - setAutoplay, - setAutoplayAnimate, - setAutoplayInterval, - setToolbarAutohide, + setWorkpadAction, + setPageAction, + setScrubberVisibleAction, + setAutoplayAction, + setAutoplayAnimateAction, + setAutoplayIntervalAction, + setToolbarAutohideAction, }; /** diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/mock/context.tsx b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/context.tsx new file mode 100644 index 00000000000000..331132afc5f645 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/context.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { CSSProperties } from 'react'; +import { + initialExternalEmbedState, + ExternalEmbedStateProvider, + useExternalEmbedState, +} from '../index'; +import { renderers as renderFunctions } from './renderers'; +import { ExternalEmbedState } from '../../types'; +import json from '../../test/hello.json'; +import { RendererSpec } from '../../../types'; + +const Container = ({ children, height, width, style }: Props) => { + const [{ refs }] = useExternalEmbedState(); + return ( + <div + className="kbnCanvas" + ref={refs.stage} + style={{ ...style, height, width, overflow: 'hidden', position: 'relative' }} + > + {children} + </div> + ); +}; + +interface Props { + children: any; + height?: number; + width?: number; + isScrubberVisible?: boolean; + style?: CSSProperties; +} +export const Context = ({ children, height, width, isScrubberVisible, style }: Props) => { + const renderers: { [key: string]: RendererSpec } = {}; + + renderFunctions.forEach(rendererFn => { + const renderer = rendererFn(); + renderers[renderer.name] = renderer; + }); + + const { footer } = initialExternalEmbedState; + + const initialState: ExternalEmbedState = { + ...initialExternalEmbedState, + footer: { + ...footer, + isScrubberVisible: isScrubberVisible || footer.isScrubberVisible, + }, + stage: { + height: 400, + page: 0, + width: 600, + }, + renderers, + workpad: json, + refs: { + stage: React.createRef(), + }, + }; + + return ( + <ExternalEmbedStateProvider initialState={initialState}> + <Container {...{ height, width, style }}>{children}</Container> + </ExternalEmbedStateProvider> + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/mock/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/index.ts new file mode 100644 index 00000000000000..94b69770505355 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './context'; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/mock/renderers.ts b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/renderers.ts new file mode 100644 index 00000000000000..4fa8c91b1dfb53 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/mock/renderers.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore Untyped local +import { error } from '../../../canvas_plugin_src/renderers/error'; +// @ts-ignore Untyped local +import { markdown } from '../../../canvas_plugin_src/renderers/markdown'; +import { metric } from '../../../canvas_plugin_src/renderers/metric'; +// @ts-ignore Untyped local +import { progress } from '../../../canvas_plugin_src/renderers/progress'; +// @ts-ignore Untyped local +import { revealImage } from '../../../canvas_plugin_src/renderers/reveal_image'; +// @ts-ignore Untyped local +import { shape } from '../../../canvas_plugin_src/renderers/shape'; +// @ts-ignore Untyped local +import { debug } from '../../../canvas_plugin_src/renderers/debug'; +// @ts-ignore Untyped local +import { image } from '../../../canvas_plugin_src/renderers/image'; +// @ts-ignore Untyped local +import { repeatImage } from '../../../canvas_plugin_src/renderers/repeat_image'; +// @ts-ignore Untyped local +import { table } from '../../../canvas_plugin_src/renderers/table'; +// @ts-ignore Untyped local +import { text } from '../../../canvas_plugin_src/renderers/text'; + +export const renderers = [ + error, + markdown, + metric, + progress, + revealImage, + shape, + debug, + image, + repeatImage, + table, + text, +]; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts b/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts index bd575c2741d318..4ed9f0dfdfb6b5 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts @@ -4,13 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExternalEmbedState } from './state'; +import { ExternalEmbedState } from '../types'; import { ExternalEmbedAction, ExternalEmbedActions } from './actions'; /** * The Action Reducer for the Embedded Canvas Workpad interface. */ -export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) => { +export const reducer = ( + state: ExternalEmbedState, + action: ExternalEmbedAction +): ExternalEmbedState => { switch (action.type) { case ExternalEmbedActions.SET_WORKPAD: { return { @@ -19,9 +22,13 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) }; } case ExternalEmbedActions.SET_PAGE: { + const { stage } = state; return { ...state, - page: action.payload.page, + stage: { + ...stage, + page: action.payload.page, + }, }; } case ExternalEmbedActions.SET_SCRUBBER_VISIBLE: { @@ -38,6 +45,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) case ExternalEmbedActions.SET_AUTOPLAY: { const { settings } = state; const { autoplay } = settings; + const { isEnabled } = action.payload; return { ...state, @@ -45,7 +53,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) ...settings, autoplay: { ...autoplay, - enabled: action.payload.autoplay, + isEnabled, }, }, }; @@ -53,7 +61,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) case ExternalEmbedActions.SET_AUTOPLAY_ANIMATE: { const { settings } = state; const { autoplay } = settings; - const { animate } = action.payload; + const { isAnimated } = action.payload; return { ...state, @@ -61,7 +69,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) ...settings, autoplay: { ...autoplay, - animate, + isAnimated, }, }, }; @@ -85,7 +93,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) case ExternalEmbedActions.SET_TOOLBAR_AUTOHIDE: { const { settings } = state; const { toolbar } = settings; - const { autohide } = action.payload; + const { isAutohide } = action.payload; return { ...state, @@ -93,7 +101,7 @@ export const reducer = (state: ExternalEmbedState, action: ExternalEmbedAction) ...settings, toolbar: { ...toolbar, - autohide, + isAutohide, }, }, }; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx b/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx index 7d82b5ea6263fc..4bd5cdba4c55c2 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx +++ b/x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx @@ -4,64 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { - createContext, - useContext, - Dispatch, - useReducer, - ReactChild, - RefObject, -} from 'react'; -import { CanvasRenderedWorkpad } from '../types'; +import React, { createContext, useContext, Dispatch, useReducer, ReactChild } from 'react'; +import { ExternalEmbedState } from '../types'; import { reducer } from './reducer'; import { ExternalEmbedAction } from './actions'; -export interface ExternalEmbedState { - renderersRegistry: { - register: (fn: Function) => void; - get: (name: string) => Function; - } | null; - workpad: CanvasRenderedWorkpad | null; - page: number; - height: number; - width: number; - footer: { - isScrubberVisible: boolean; - }; - settings: { - autoplay: { - enabled: boolean; - interval: string; - animate: boolean; - }; - toolbar: { - autohide: boolean; - }; - }; - refs: { - stage: RefObject<HTMLDivElement>; - }; -} - type StateType = [ExternalEmbedState, Dispatch<ExternalEmbedAction>]; export const initialExternalEmbedState: ExternalEmbedState = { - renderersRegistry: null, + renderers: {}, workpad: null, - page: 0, - height: 0, - width: 0, + stage: { + page: 0, + height: 0, + width: 0, + }, footer: { isScrubberVisible: false, }, settings: { autoplay: { - enabled: false, + isEnabled: false, interval: '5s', - animate: false, + isAnimated: false, }, toolbar: { - autohide: false, + isAutohide: false, }, }, refs: { diff --git a/x-pack/legacy/plugins/canvas/external_runtime/css_modules.d.ts b/x-pack/legacy/plugins/canvas/external_runtime/css_modules.d.ts new file mode 100644 index 00000000000000..a12eaabffce14f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/css_modules.d.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +declare module '*.scss' { + const styles: { [className: string]: string }; + // eslint-disable-next-line + export default styles; +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/index.html b/x-pack/legacy/plugins/canvas/external_runtime/index.html index d27356c97c8609..a2d572ac2ce38a 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/index.html +++ b/x-pack/legacy/plugins/canvas/external_runtime/index.html @@ -5,7 +5,7 @@ </head> <body> - <div kbn-canvas-embed="canvas" kbn-canvas-height="400" kbn-canvas-url="/test/austin.json"></div> + <div kbn-canvas-embed="canvas" kbn-canvas-height="400" kbn-canvas-url="/test/hello.json"></div> </body> <script type="text/javascript"> KbnCanvas.embed(); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/test/hello.json b/x-pack/legacy/plugins/canvas/external_runtime/test/hello.json new file mode 100644 index 00000000000000..7357c363b37921 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/test/hello.json @@ -0,0 +1,88 @@ +{ + "pages": [ + { + "id": "page-7186b301-f8a7-4c65-8b89-38d68d31cfc4", + "style": { "background": "#777777" }, + "transition": {}, + "groups": [], + "elements": [ + { + "id": "element-e48b0339-4829-44eb-9f0c-8787e693a085", + "position": { + "left": -1, + "top": 264.78145695364236, + "width": 1082, + "height": 205.37748344370857, + "angle": 0, + "parent": null + }, + "expressionRenderable": { + "state": "ready", + "value": { + "type": "render", + "as": "markdown", + "value": { + "content": "# Hello, Canvas.", + "font": { + "type": "style", + "spec": { + "fontFamily": "'Open Sans', Helvetica, Arial, sans-serif", + "fontWeight": "normal", + "fontStyle": "normal", + "textDecoration": "none", + "textAlign": "left", + "fontSize": "14px", + "lineHeight": "1" + }, + "css": "font-family:'Open Sans', Helvetica, Arial, sans-serif;font-weight:normal;font-style:normal;text-decoration:none;text-align:left;font-size:14px;line-height:1" + } + }, + "css": ".canvasRenderEl h1 {\n font-size: 150px;\n text-align: center;\n color: #d3d3d3;\n}", + "containerStyle": { "type": "containerStyle", "overflow": "hidden" } + }, + "error": null + } + } + ] + } + ], + "name": "My Canvas Workpad", + "id": "workpad-bde237d4-12d7-4c3b-9ce8-8646d999618e", + "width": 1080, + "height": 720, + "css": ".canvasPage {\n\n}", + "page": 0, + "colors": [ + "#37988d", + "#c19628", + "#b83c6f", + "#3f9939", + "#1785b0", + "#ca5f35", + "#45bdb0", + "#f2bc33", + "#e74b8b", + "#4fbf48", + "#1ea6dc", + "#fd7643", + "#72cec3", + "#f5cc5d", + "#ec77a8", + "#7acf74", + "#4cbce4", + "#fd986f", + "#a1ded7", + "#f8dd91", + "#f2a4c5", + "#a6dfa2", + "#86d2ed", + "#fdba9f", + "#000000", + "#444444", + "#777777", + "#BBBBBB", + "#FFFFFF", + "rgba(255,255,255,0)" + ], + "isWriteable": true +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/types.ts b/x-pack/legacy/plugins/canvas/external_runtime/types.ts index ad719b5cee9ab8..79db5443a9a2f2 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/types.ts +++ b/x-pack/legacy/plugins/canvas/external_runtime/types.ts @@ -4,18 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RefObject } from 'react'; // @ts-ignore Unlinked Webpack Type import ContainerStyle from 'types/interpreter'; -import { CSSProperties } from 'react'; import { SavedObject, SavedObjectAttributes } from 'src/core/server'; -import { CanvasElement, CanvasPage, CanvasWorkpad } from '../types'; +import { ElementPosition, CanvasPage, CanvasWorkpad, RendererSpec } from '../types'; /** * Represents a Canvas Element whose expression has been evaluated and now * exists in a transient, ready-to-render state. */ -export interface CanvasRenderedElement extends CanvasElement { +export interface CanvasRenderedElement { + id: string; + position: ElementPosition; expressionRenderable: CanvasRenderable; } @@ -23,7 +25,7 @@ export interface CanvasRenderedElement extends CanvasElement { * Represents a Page within a Canvas Workpad that is made up of ready-to- * render Elements. */ -export interface CanvasRenderedPage extends CanvasPage { +export interface CanvasRenderedPage extends Omit<Omit<CanvasPage, 'elements'>, 'groups'> { elements: CanvasRenderedElement[]; groups: CanvasRenderedElement[][]; } @@ -31,7 +33,7 @@ export interface CanvasRenderedPage extends CanvasPage { /** * A Canvas Workpad made up of ready-to-render Elements. */ -export interface CanvasRenderedWorkpad extends CanvasWorkpad { +export interface CanvasRenderedWorkpad extends Omit<CanvasWorkpad, 'pages'> { pages: CanvasRenderedPage[]; } @@ -45,13 +47,49 @@ export type CanvasRenderedWorkpadSavedObject = SavedObject< * upon a stage. */ export interface CanvasRenderable { - error: string; + error: string | null; state: 'ready' | 'error'; value: { as: string; containerStyle: ContainerStyle; - css: CSSProperties; + css: string; type: 'render'; value: any; }; } + +export interface ExternalEmbedState { + renderers: { [key: string]: RendererSpec }; + workpad: CanvasRenderedWorkpad | null; + stage: Stage; + footer: { + isScrubberVisible: boolean; + }; + settings: Settings; + refs: Refs; +} + +export interface Stage { + page: number; + height: number; + width: number; +} + +export interface Refs { + stage: RefObject<HTMLDivElement>; +} + +export interface Settings { + autoplay: AutoplaySettings; + toolbar: ToolbarSettings; +} + +export interface AutoplaySettings { + isEnabled: boolean; + interval: string; + isAnimated: boolean; +} + +export interface ToolbarSettings { + isAutohide: boolean; +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js b/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js index ba59ba7624ed0d..63bf47bf53b3e6 100644 --- a/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js @@ -27,10 +27,6 @@ module.exports = { resolve: { alias: { ui: path.resolve(KIBANA_ROOT, 'src/legacy/ui/public'), - 'data/interpreter': path.resolve( - KIBANA_ROOT, - 'src/plugins/data/public/expressions/interpreter' - ), 'kbn/interpreter': path.resolve(KIBANA_ROOT, 'packages/kbn-interpreter/target/common'), 'types/interpreter': path.resolve( KIBANA_ROOT, diff --git a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts index bcf3b8b7279fc7..40df324f25a8df 100644 --- a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts @@ -422,9 +422,9 @@ export function getAutoplay(state: State): State['transient']['autoplay'] { return get(state, 'transient.autoplay'); } -export function getRenderedWorkpad(state) { +export function getRenderedWorkpad(state: State) { const currentPages = getPages(state); - const args = get(state, ['transient', 'resolvedArgs']); + const args = get<State, State['transient']['resolvedArgs']>(state, ['transient', 'resolvedArgs']); const renderedPages = currentPages.map(page => { const { elements, ...rest } = page; return {