mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[feature][Canvas] Share Workpads in other Websites (#46278)
* [Canvas] Embedding Workpads in other Websites (#42545) * Testing for Workpad Snapshots * Rename Snapshots to Shareables; update documentation. (#46513) * [canvas][shareables] Add Localization + Tweaks (#46632) * Add localization + tweak naming * Fix duplicate key * Update storyshots * [shareables] Unsupported Renderer Warning (#46862) * [shareables] Unsupported Renderer Warning * Update snapshots; add test * Addressing Feedback * [canvas][shareables] Simplify and complete testing (#47328) * Simplify * Updates * Finishing up * A few tweaks * Fix eslint errors; how would those happen?? * Fix CI build of runtime; assorted visual tweaks * Update x-pack/legacy/plugins/canvas/shareable_runtime/test/index.ts Co-Authored-By: Spencer <email@spalger.com> * Addressing feedback * Remove null-loader from root package * re-add null-loader until mitigation is found * [perf] Fix unsupported renderers performance issue (#47769) * [perf] Fix perf issue with unsupported renderers * Fixing snapshots * Addressing review feedback (#47775) * Addressing feedback * Addressing feedback (#47883) * Branding Changes (#47913) * Branding Changes * Update snapshots
This commit is contained in:
parent
0a85478d41
commit
27cbdf5f50
121 changed files with 46883 additions and 188 deletions
|
@ -7,6 +7,8 @@ files:
|
|||
- 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss'
|
||||
- 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss'
|
||||
- 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss'
|
||||
ignore:
|
||||
- 'x-pack/legacy/plugins/canvas/shareable_runtime/**/*.s+(a|c)ss'
|
||||
- 'x-pack/legacy/plugins/lens/**/*.s+(a|c)ss'
|
||||
- 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss'
|
||||
rules:
|
||||
|
|
|
@ -625,6 +625,14 @@
|
|||
'@types/zen-observable',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'archiver',
|
||||
groupName: 'archiver related packages',
|
||||
packageNames: [
|
||||
'archiver',
|
||||
'@types/archiver',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'base64-js',
|
||||
groupName: 'base64-js related packages',
|
||||
|
|
|
@ -86,7 +86,7 @@ export async function buildDistributables(options) {
|
|||
const config = await getConfig({
|
||||
isRelease,
|
||||
versionQualifier,
|
||||
targetAllPlatforms
|
||||
targetAllPlatforms,
|
||||
});
|
||||
|
||||
const run = createRunner({
|
||||
|
@ -143,16 +143,20 @@ export async function buildDistributables(options) {
|
|||
* package platform-specific builds into archives
|
||||
* or os-specific packages in the target directory
|
||||
*/
|
||||
if (createArchives) { // control w/ --skip-archives
|
||||
if (createArchives) {
|
||||
// control w/ --skip-archives
|
||||
await run(CreateArchivesTask);
|
||||
}
|
||||
if (createDebPackage) { // control w/ --deb or --skip-os-packages
|
||||
if (createDebPackage) {
|
||||
// control w/ --deb or --skip-os-packages
|
||||
await run(CreateDebPackageTask);
|
||||
}
|
||||
if (createRpmPackage) { // control w/ --rpm or --skip-os-packages
|
||||
if (createRpmPackage) {
|
||||
// control w/ --rpm or --skip-os-packages
|
||||
await run(CreateRpmPackageTask);
|
||||
}
|
||||
if (createDockerPackage) { // control w/ --docker or --skip-os-packages
|
||||
if (createDockerPackage) {
|
||||
// control w/ --docker or --skip-os-packages
|
||||
await run(CreateDockerPackageTask);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,39 +29,36 @@ export const CleanTask = {
|
|||
description: 'Cleaning artifacts from previous builds',
|
||||
|
||||
async run(config, log) {
|
||||
await deleteAll([
|
||||
config.resolveFromRepo('build'),
|
||||
config.resolveFromRepo('target'),
|
||||
config.resolveFromRepo('.node_binaries'),
|
||||
], log);
|
||||
await deleteAll(
|
||||
[
|
||||
config.resolveFromRepo('build'),
|
||||
config.resolveFromRepo('target'),
|
||||
config.resolveFromRepo('.node_binaries'),
|
||||
],
|
||||
log
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CleanPackagesTask = {
|
||||
description:
|
||||
'Cleaning source for packages that are now installed in node_modules',
|
||||
description: 'Cleaning source for packages that are now installed in node_modules',
|
||||
|
||||
async run(config, log, build) {
|
||||
await deleteAll([
|
||||
build.resolvePath('packages'),
|
||||
build.resolvePath('yarn.lock'),
|
||||
], log);
|
||||
await deleteAll([build.resolvePath('packages'), build.resolvePath('yarn.lock')], log);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const CleanTypescriptTask = {
|
||||
description:
|
||||
'Cleaning typescript source files that have been transpiled to JS',
|
||||
description: 'Cleaning typescript source files that have been transpiled to JS',
|
||||
|
||||
async run(config, log, build) {
|
||||
log.info('Deleted %d files', await scanDelete({
|
||||
directory: build.resolvePath(),
|
||||
regularExpressions: [
|
||||
/\.(ts|tsx|d\.ts)$/,
|
||||
/tsconfig.*\.json$/
|
||||
]
|
||||
}));
|
||||
log.info(
|
||||
'Deleted %d files',
|
||||
await scanDelete({
|
||||
directory: build.resolvePath(),
|
||||
regularExpressions: [/\.(ts|tsx|d\.ts)$/, /tsconfig.*\.json$/],
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -70,9 +67,7 @@ export const CleanExtraFilesFromModulesTask = {
|
|||
|
||||
async run(config, log, build) {
|
||||
const makeRegexps = patterns =>
|
||||
patterns.map(pattern =>
|
||||
minimatch.makeRe(pattern, { nocase: true })
|
||||
);
|
||||
patterns.map(pattern => minimatch.makeRe(pattern, { nocase: true }));
|
||||
|
||||
const regularExpressions = makeRegexps([
|
||||
// tests
|
||||
|
@ -169,19 +164,23 @@ export const CleanExtraFilesFromModulesTask = {
|
|||
'**/docker-compose.yml',
|
||||
]);
|
||||
|
||||
log.info('Deleted %d files', await scanDelete({
|
||||
directory: build.resolvePath('node_modules'),
|
||||
regularExpressions,
|
||||
excludePaths: [
|
||||
build.resolvePath('node_modules/@elastic/ctags-langserver/vendor')
|
||||
]
|
||||
}));
|
||||
log.info(
|
||||
'Deleted %d files',
|
||||
await scanDelete({
|
||||
directory: build.resolvePath('node_modules'),
|
||||
regularExpressions,
|
||||
excludePaths: [build.resolvePath('node_modules/@elastic/ctags-langserver/vendor')],
|
||||
})
|
||||
);
|
||||
|
||||
if (!build.isOss()) {
|
||||
log.info('Deleted %d files', await scanDelete({
|
||||
directory: build.resolvePath('x-pack/node_modules'),
|
||||
regularExpressions
|
||||
}));
|
||||
log.info(
|
||||
'Deleted %d files',
|
||||
await scanDelete({
|
||||
directory: build.resolvePath('x-pack/node_modules'),
|
||||
regularExpressions,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -192,14 +191,15 @@ export const CleanExtraBinScriptsTask = {
|
|||
async run(config, log, build) {
|
||||
for (const platform of config.getNodePlatforms()) {
|
||||
if (platform.isWindows()) {
|
||||
await deleteAll([
|
||||
build.resolvePathForPlatform(platform, 'bin', '*'),
|
||||
`!${build.resolvePathForPlatform(platform, 'bin', '*.bat')}`,
|
||||
], log);
|
||||
await deleteAll(
|
||||
[
|
||||
build.resolvePathForPlatform(platform, 'bin', '*'),
|
||||
`!${build.resolvePathForPlatform(platform, 'bin', '*.bat')}`,
|
||||
],
|
||||
log
|
||||
);
|
||||
} else {
|
||||
await deleteAll([
|
||||
build.resolvePathForPlatform(platform, 'bin', '*.bat'),
|
||||
], log);
|
||||
await deleteAll([build.resolvePathForPlatform(platform, 'bin', '*.bat')], log);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -251,14 +251,10 @@ export const CleanEmptyFoldersTask = {
|
|||
// Delete every single empty folder from
|
||||
// the distributable except the plugins
|
||||
// and data folder.
|
||||
await deleteEmptyFolders(
|
||||
log,
|
||||
build.resolvePath('.'),
|
||||
[
|
||||
build.resolvePath('plugins'),
|
||||
build.resolvePath('data')
|
||||
]
|
||||
);
|
||||
await deleteEmptyFolders(log, build.resolvePath('.'), [
|
||||
build.resolvePath('plugins'),
|
||||
build.resolvePath('data'),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -266,7 +262,7 @@ export const CleanCtagBuildTask = {
|
|||
description: 'Cleaning extra platform-specific files from @elastic/node-ctag build dir',
|
||||
|
||||
async run(config, log, build) {
|
||||
const getPlatformId = (platform) => {
|
||||
const getPlatformId = platform => {
|
||||
if (platform.isWindows()) {
|
||||
return 'win32';
|
||||
} else if (platform.isLinux()) {
|
||||
|
@ -283,11 +279,17 @@ export const CleanCtagBuildTask = {
|
|||
}
|
||||
|
||||
const ctagsBuildDir = build.resolvePathForPlatform(platform, RELATIVE_CTAGS_BUILD_DIR);
|
||||
await deleteAll([
|
||||
resolve(ctagsBuildDir, '*'),
|
||||
`!${resolve(ctagsBuildDir, `ctags-node-v${process.versions.modules}-${getPlatformId(platform)}-x64`)}`
|
||||
], log);
|
||||
await deleteAll(
|
||||
[
|
||||
resolve(ctagsBuildDir, '*'),
|
||||
`!${resolve(
|
||||
ctagsBuildDir,
|
||||
`ctags-node-v${process.versions.modules}-${getPlatformId(platform)}-x64`
|
||||
)}`,
|
||||
],
|
||||
log
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
36
src/dev/jest/mocks/css_module_mock.js
Normal file
36
src/dev/jest/mocks/css_module_mock.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This proxy allows for CSS Modules to be interpreted properly by
|
||||
* Jest. Given a CSS Module class `thisClass`, we'd expect it to
|
||||
* be obfuscated at runtime. With this mock, `thisClass` will be
|
||||
* returned. This allows for consistent enzyme and snapshot tests.
|
||||
*/
|
||||
module.exports = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: function getter(target, key) {
|
||||
if (key === '__esModule') {
|
||||
return false;
|
||||
}
|
||||
return key;
|
||||
},
|
||||
}
|
||||
);
|
|
@ -28,7 +28,9 @@
|
|||
"!legacy/plugins/**/*.test.{js,ts}",
|
||||
"!legacy/plugins/**/__snapshots__",
|
||||
"!legacy/plugins/**/__snapshots__/*",
|
||||
"!legacy/plugins/**/__mocks__/*"
|
||||
"!legacy/plugins/**/__mocks__/*",
|
||||
"!legacy/plugins/canvas/shareable_runtime/test",
|
||||
"!legacy/plugins/canvas/shareable_runtime/test/**/*"
|
||||
],
|
||||
"skipInstallDependencies": true
|
||||
}
|
||||
|
|
|
@ -24,14 +24,13 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) {
|
|||
'^plugins/([^/.]*)(.*)': `${kibanaDirectory}/src/legacy/core_plugins/$1/public$2`,
|
||||
'^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`,
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath,
|
||||
'\\.module.(css|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/css_module_mock.js`,
|
||||
'\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`,
|
||||
'^test_utils/enzyme_helpers': `${xPackKibanaDirectory}/test_utils/enzyme_helpers.tsx`,
|
||||
'^test_utils/find_test_subject': `${xPackKibanaDirectory}/test_utils/find_test_subject.ts`,
|
||||
},
|
||||
coverageDirectory: '<rootDir>/../target/kibana-coverage/jest',
|
||||
coverageReporters: [
|
||||
'html',
|
||||
],
|
||||
coverageReporters: ['html'],
|
||||
setupFiles: [
|
||||
`${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`,
|
||||
`<rootDir>/dev-tools/jest/setup/polyfills.js`,
|
||||
|
|
|
@ -12,6 +12,11 @@ import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-story
|
|||
import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer';
|
||||
import { addSerializer } from 'jest-specific-snapshot';
|
||||
|
||||
// Several of the renderers, used by the runtime, use jQuery.
|
||||
import jquery from 'jquery';
|
||||
global.$ = jquery;
|
||||
global.jQuery = jquery;
|
||||
|
||||
// Set our default timezone to UTC for tests so we can generate predictable snapshots
|
||||
moment.tz.setDefault('UTC');
|
||||
|
||||
|
@ -53,6 +58,19 @@ jest.mock(
|
|||
}
|
||||
);
|
||||
|
||||
// Disabling this test due to https://github.com/elastic/eui/issues/2242
|
||||
jest.mock(
|
||||
'../public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.examples',
|
||||
() => {
|
||||
return 'Disabled Panel';
|
||||
}
|
||||
);
|
||||
|
||||
// This element uses a `ref` and cannot be rendered by Jest snapshots.
|
||||
import { RenderedElement } from '../shareable_runtime/components/rendered_element';
|
||||
jest.mock('../shareable_runtime/components/rendered_element');
|
||||
RenderedElement.mockImplementation(() => 'RenderedElement');
|
||||
|
||||
addSerializer(styleSheetSerializer);
|
||||
|
||||
// Initialize Storyshots and build the Jest Snapshots
|
||||
|
|
|
@ -57,6 +57,56 @@ module.exports = async ({ config }) => {
|
|||
],
|
||||
});
|
||||
|
||||
// Enable SASS, but exclude CSS Modules in Storybook
|
||||
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' },
|
||||
],
|
||||
});
|
||||
|
||||
// Enable CSS Modules in Storybook
|
||||
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',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Ensure jQuery is global for Storybook, specifically for the runtime.
|
||||
config.plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
$: 'jquery',
|
||||
jQuery: 'jquery',
|
||||
})
|
||||
);
|
||||
|
||||
// Reference the built DLL file of static(ish) dependencies, which are removed
|
||||
// during kbn:bootstrap and rebuilt if missing.
|
||||
config.plugins.push(
|
||||
|
@ -109,8 +159,8 @@ module.exports = async ({ config }) => {
|
|||
})
|
||||
);
|
||||
|
||||
// Tell Webpack about the ts/x extensions
|
||||
config.resolve.extensions.push('.ts', '.tsx');
|
||||
// Tell Webpack about relevant 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
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SHAREABLE_RUNTIME_NAME } from '../../shareable_runtime/constants';
|
||||
|
||||
export const CANVAS_TYPE = 'canvas-workpad';
|
||||
export const CUSTOM_ELEMENT_TYPE = 'canvas-element';
|
||||
export const CANVAS_APP = 'canvas';
|
||||
|
@ -33,3 +35,7 @@ export const CANVAS_LAYOUT_STAGE_CONTENT_SELECTOR = `canvasLayout__stageContent`
|
|||
export const DATATABLE_COLUMN_TYPES = ['string', 'number', 'null', 'boolean', 'date'];
|
||||
export const LAUNCHED_FULLSCREEN = 'workpad-full-screen-launch';
|
||||
export const LAUNCHED_FULLSCREEN_AUTOPLAY = 'workpad-full-screen-launch-with-autoplay';
|
||||
export const API_ROUTE_SHAREABLE_BASE = '/public/canvas';
|
||||
export const API_ROUTE_SHAREABLE_ZIP = `${API_ROUTE_SHAREABLE_BASE}/zip`;
|
||||
export const API_ROUTE_SHAREABLE_RUNTIME = `${API_ROUTE_SHAREABLE_BASE}/runtime`;
|
||||
export const API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD = `${API_ROUTE_SHAREABLE_BASE}/${SHAREABLE_RUNTIME_NAME}.js`;
|
||||
|
|
|
@ -15,3 +15,13 @@ export const fetch = axios.create({
|
|||
},
|
||||
timeout: FETCH_TIMEOUT,
|
||||
});
|
||||
|
||||
export const arrayBufferFetch = axios.create({
|
||||
responseType: 'arraybuffer',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'kbn-xsrf': 'professionally-crafted-string-of-text',
|
||||
},
|
||||
timeout: FETCH_TIMEOUT,
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CANVAS, JSON, KIBANA, PDF, POST, URL } from './constants';
|
||||
import { CANVAS, HTML, JSON, KIBANA, PDF, POST, URL, ZIP } from './constants';
|
||||
|
||||
export const ComponentStrings = {
|
||||
AddEmbeddableFlyout: {
|
||||
|
@ -463,6 +463,144 @@ export const ComponentStrings = {
|
|||
defaultMessage: 'Delete',
|
||||
}),
|
||||
},
|
||||
ShareWebsiteFlyout: {
|
||||
getRuntimeStepTitle: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadRuntimeTitle', {
|
||||
defaultMessage: 'Download runtime',
|
||||
}),
|
||||
getSnippentsStepTitle: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.addSnippetsTitle', {
|
||||
defaultMessage: 'Add snippets to website',
|
||||
}),
|
||||
getStepsDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.description', {
|
||||
defaultMessage:
|
||||
'Follow these steps to share a static version of this workpad on an external website. It will be a visual snapshot of the current workpad, and will not have access to live data.',
|
||||
}),
|
||||
getTitle: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.flyoutTitle', {
|
||||
defaultMessage: 'Share on a website',
|
||||
}),
|
||||
getUnsupportedRendererWarning: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.unsupportedRendererWarning', {
|
||||
defaultMessage:
|
||||
'This workpad contains render functions that are not supported by the {CANVAS} Shareable Workpad Runtime. These elements will not be rendered:',
|
||||
values: {
|
||||
CANVAS,
|
||||
},
|
||||
}),
|
||||
getWorkpadStepTitle: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadWorkpadTitle', {
|
||||
defaultMessage: 'Download workpad',
|
||||
}),
|
||||
},
|
||||
ShareWebsiteRuntimeStep: {
|
||||
getDownloadLabel: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.runtimeStep.downloadLabel', {
|
||||
defaultMessage: 'Download runtime',
|
||||
}),
|
||||
getStepDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.runtimeStep.description', {
|
||||
defaultMessage:
|
||||
'In order to render a Shareable Workpad, you also need to include the {CANVAS} Shareable Workpad Runtime. You can skip this step if the runtime is already included on your website.',
|
||||
values: {
|
||||
CANVAS,
|
||||
},
|
||||
}),
|
||||
},
|
||||
ShareWebsiteSnippetsStep: {
|
||||
getAutoplayParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.autoplayParameterDescription', {
|
||||
defaultMessage: 'Should the runtime automatically move through the pages of the workpad?',
|
||||
}),
|
||||
getCallRuntimeLabel: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.callRuntimeLabel', {
|
||||
defaultMessage: 'Call Runtime',
|
||||
}),
|
||||
getHeightParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.heightParameterDescription', {
|
||||
defaultMessage: 'The height of the Workpad. Defaults to the Workpad height.',
|
||||
}),
|
||||
getIncludeRuntimeLabel: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.includeRuntimeLabel', {
|
||||
defaultMessage: 'Include Runtime',
|
||||
}),
|
||||
getIntervalParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.intervalParameterDescription', {
|
||||
defaultMessage:
|
||||
'The interval upon which the pages will advance in time format, (e.g. {twoSeconds}, {oneMinute})',
|
||||
values: {
|
||||
twoSeconds: '2s',
|
||||
oneMinute: '1m',
|
||||
},
|
||||
}),
|
||||
getPageParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.pageParameterDescription', {
|
||||
defaultMessage: 'The page to display. Defaults to the page specified by the Workpad.',
|
||||
}),
|
||||
getParametersDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.parametersDescription', {
|
||||
defaultMessage:
|
||||
'There are a number of inline parameters to configure the Shareable Workpad.',
|
||||
}),
|
||||
getParametersTitle: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.parametersLabel', {
|
||||
defaultMessage: 'Parameters',
|
||||
}),
|
||||
getPlaceholderLabel: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.placeholderLabel', {
|
||||
defaultMessage: 'Placeholder',
|
||||
}),
|
||||
getRequiredLabel: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.requiredLabel', {
|
||||
defaultMessage: 'required',
|
||||
}),
|
||||
getShareableParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.shareableParameterDescription', {
|
||||
defaultMessage: 'The type of shareable. In this case, a {CANVAS} Workpad.',
|
||||
values: {
|
||||
CANVAS,
|
||||
},
|
||||
}),
|
||||
getSnippetsStepDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.description', {
|
||||
defaultMessage:
|
||||
'The Workpad is placed within the {HTML} of the site by using an {HTML} placeholder. Parameters for the runtime are included inline. See the full list of parameters below. You can include more than one workpad on the page.',
|
||||
values: {
|
||||
HTML,
|
||||
},
|
||||
}),
|
||||
getToolbarParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.toolbarParameterDescription', {
|
||||
defaultMessage: 'Should the toolbar be hidden?',
|
||||
}),
|
||||
getUrlParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.urlParameterDescription', {
|
||||
defaultMessage: 'The {URL} of the Shareable Workpad {JSON} file.',
|
||||
values: {
|
||||
URL,
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
getWidthParameterDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.widthParameterDescription', {
|
||||
defaultMessage: 'The width of the Workpad. Defaults to the Workpad width.',
|
||||
}),
|
||||
},
|
||||
ShareWebsiteWorkpadStep: {
|
||||
getDownloadLabel: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.workpadStep.downloadLabel', {
|
||||
defaultMessage: 'Download workpad',
|
||||
}),
|
||||
getStepDescription: () =>
|
||||
i18n.translate('xpack.canvas.shareWebsiteFlyout.workpadStep.description', {
|
||||
defaultMessage:
|
||||
'The workpad will be exported as a single {JSON} file for sharing in another site.',
|
||||
values: {
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
},
|
||||
SidebarContent: {
|
||||
getGroupedElementSidebarTitle: () =>
|
||||
i18n.translate('xpack.canvas.sidebarContent.groupedElementSidebarTitle', {
|
||||
|
@ -816,6 +954,10 @@ export const ComponentStrings = {
|
|||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.copyReportingConfigMessage', {
|
||||
defaultMessage: 'Copied reporting configuration to clipboard',
|
||||
}),
|
||||
getCopyShareConfigMessage: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.copyShareConfigMessage', {
|
||||
defaultMessage: 'Copied share markup to clipboard',
|
||||
}),
|
||||
getExportPDFErrorTitle: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.exportPDFErrorMessage', {
|
||||
defaultMessage: "Failed to create {PDF} for '{workpadName}'",
|
||||
|
@ -881,6 +1023,15 @@ export const ComponentStrings = {
|
|||
PDF,
|
||||
},
|
||||
}),
|
||||
getShareableZipErrorTitle: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteErrorTitle', {
|
||||
defaultMessage:
|
||||
"Failed to create {ZIP} file for '{workpadName}'. The workpad may be too large. You'll need to download the files separately.",
|
||||
values: {
|
||||
ZIP,
|
||||
workpadName,
|
||||
},
|
||||
}),
|
||||
getShareDownloadJSONTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareDownloadJSONTitle', {
|
||||
defaultMessage: 'Download as {JSON}',
|
||||
|
@ -895,6 +1046,10 @@ export const ComponentStrings = {
|
|||
PDF,
|
||||
},
|
||||
}),
|
||||
getShareWebsiteTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteTitle', {
|
||||
defaultMessage: 'Share on a website',
|
||||
}),
|
||||
getShareWorkpadMessage: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareWorkpadMessage', {
|
||||
defaultMessage: 'Share this workpad',
|
||||
|
|
|
@ -41,3 +41,4 @@ export const TYPE_NUMBER = '`number`';
|
|||
export const TYPE_STRING = '`string`';
|
||||
export const URL = 'URL';
|
||||
export const UTC = 'UTC';
|
||||
export const ZIP = 'ZIP';
|
||||
|
|
|
@ -22,6 +22,24 @@ export const ErrorStrings = {
|
|||
i18n.translate('xpack.canvas.error.downloadWorkpad.downloadFailureErrorMessage', {
|
||||
defaultMessage: "Couldn't download workpad",
|
||||
}),
|
||||
getDownloadRenderedWorkpadFailureErrorMessage: () =>
|
||||
i18n.translate(
|
||||
'xpack.canvas.error.downloadWorkpad.downloadRenderedWorkpadFailureErrorMessage',
|
||||
{
|
||||
defaultMessage: "Couldn't download rendered workpad",
|
||||
}
|
||||
),
|
||||
getDownloadRuntimeFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.downloadWorkpad.downloadRuntimeFailureErrorMessage', {
|
||||
defaultMessage: "Couldn't download Shareable Runtime",
|
||||
}),
|
||||
getDownloadZippedRuntimeFailureErrorMessage: () =>
|
||||
i18n.translate(
|
||||
'xpack.canvas.error.downloadWorkpad.downloadZippedRuntimeFailureErrorMessage',
|
||||
{
|
||||
defaultMessage: "Couldn't download ZIP file",
|
||||
}
|
||||
),
|
||||
},
|
||||
esPersist: {
|
||||
getSaveFailureTitle: () =>
|
||||
|
|
|
@ -17,7 +17,7 @@ const { WorkpadHeaderCustomInterval: strings } = ComponentStrings;
|
|||
interface Props {
|
||||
gutterSize: FlexGroupGutterSize;
|
||||
buttonSize: ButtonSize;
|
||||
onSubmit: (interval: number | undefined) => void;
|
||||
onSubmit: (interval: number) => void;
|
||||
defaultValue: any;
|
||||
}
|
||||
|
||||
|
@ -32,8 +32,9 @@ export const CustomInterval = ({ gutterSize, buttonSize, onSubmit, defaultValue
|
|||
<form
|
||||
onSubmit={ev => {
|
||||
ev.preventDefault();
|
||||
|
||||
onSubmit(refreshInterval);
|
||||
if (!isInvalid && refreshInterval) {
|
||||
onSubmit(refreshInterval);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup gutterSize={gutterSize}>
|
||||
|
|
|
@ -1,85 +1,89 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots components/Export/WorkpadExport disabled 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownCenter"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
className="euiPopover euiPopover--anchorDownCenter"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Share this workpad"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
aria-label="Share this workpad"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/Export/WorkpadExport enabled 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownCenter"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
className="euiPopover euiPopover--anchorDownCenter"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Share this workpad"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
aria-label="Share this workpad"
|
||||
className="euiButtonIcon euiButtonIcon--primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
import { ShareWebsiteFlyout } from '../share_website_flyout';
|
||||
|
||||
storiesOf('components/Export/ShareWebsiteFlyout', module)
|
||||
.addParameters({
|
||||
info: {
|
||||
inline: true,
|
||||
styles: {
|
||||
infoBody: {
|
||||
margin: 20,
|
||||
},
|
||||
infoStory: {
|
||||
margin: '20px 30px',
|
||||
width: '620px',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.add('default', () => (
|
||||
<ShareWebsiteFlyout
|
||||
onCopy={action('onCopy')}
|
||||
onDownload={action('onDownload')}
|
||||
onClose={action('onClose')}
|
||||
/>
|
||||
))
|
||||
.add('unsupported renderers', () => (
|
||||
<ShareWebsiteFlyout
|
||||
onCopy={action('onCopy')}
|
||||
onDownload={action('onDownload')}
|
||||
onClose={action('onClose')}
|
||||
unsupportedRenderers={['rendererOne', 'rendererTwo']}
|
||||
/>
|
||||
));
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 chrome from 'ui/chrome';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose, withProps } from 'recompose';
|
||||
// @ts-ignore Untyped local
|
||||
import {
|
||||
getWorkpad,
|
||||
getRenderedWorkpad,
|
||||
getRenderedWorkpadExpressions,
|
||||
} from '../../../../state/selectors/workpad';
|
||||
// @ts-ignore Untyped local
|
||||
import { notify } from '../../../../lib/notify';
|
||||
// @ts-ignore Untyped local
|
||||
import {
|
||||
downloadRenderedWorkpad,
|
||||
downloadRuntime,
|
||||
downloadZippedRuntime,
|
||||
// @ts-ignore Untyped local
|
||||
} from '../../../../lib/download_workpad';
|
||||
import { ShareWebsiteFlyout as Component, Props as ComponentProps } from './share_website_flyout';
|
||||
import { State, CanvasWorkpad } from '../../../../../types';
|
||||
import { CanvasRenderedWorkpad } from '../../../../../shareable_runtime/types';
|
||||
// @ts-ignore Untyped local.
|
||||
import { fetch, arrayBufferFetch } from '../../../../../common/lib/fetch';
|
||||
import { API_ROUTE_SHAREABLE_ZIP } from '../../../../../common/lib/constants';
|
||||
import { renderFunctionNames } from '../../../../../shareable_runtime/supported_renderers';
|
||||
|
||||
import { ComponentStrings } from '../../../../../i18n';
|
||||
import { OnCloseFn } from '../workpad_export';
|
||||
const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings;
|
||||
|
||||
const getUnsupportedRenderers = (state: State) => {
|
||||
const renderers: string[] = [];
|
||||
const expressions = getRenderedWorkpadExpressions(state);
|
||||
expressions.forEach(expression => {
|
||||
if (!renderFunctionNames.includes(expression)) {
|
||||
renderers.push(expression);
|
||||
}
|
||||
});
|
||||
|
||||
return renderers;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
renderedWorkpad: getRenderedWorkpad(state),
|
||||
unsupportedRenderers: getUnsupportedRenderers(state),
|
||||
workpad: getWorkpad(state),
|
||||
});
|
||||
|
||||
interface Props {
|
||||
onClose: OnCloseFn;
|
||||
renderedWorkpad: CanvasRenderedWorkpad;
|
||||
unsupportedRenderers: string[];
|
||||
workpad: CanvasWorkpad;
|
||||
}
|
||||
|
||||
export const ShareWebsiteFlyout = compose<ComponentProps, Pick<Props, 'onClose'>>(
|
||||
connect(mapStateToProps),
|
||||
withProps(
|
||||
({ unsupportedRenderers, renderedWorkpad, onClose, workpad }: Props): ComponentProps => ({
|
||||
unsupportedRenderers,
|
||||
onClose,
|
||||
onCopy: () => {
|
||||
notify.info(strings.getCopyShareConfigMessage());
|
||||
},
|
||||
onDownload: type => {
|
||||
switch (type) {
|
||||
case 'share':
|
||||
downloadRenderedWorkpad(renderedWorkpad);
|
||||
return;
|
||||
case 'shareRuntime':
|
||||
downloadRuntime();
|
||||
return;
|
||||
case 'shareZip':
|
||||
const basePath = chrome.getBasePath();
|
||||
arrayBufferFetch
|
||||
.post(`${basePath}${API_ROUTE_SHAREABLE_ZIP}`, JSON.stringify(renderedWorkpad))
|
||||
.then(blob => downloadZippedRuntime(blob.data))
|
||||
.catch((err: Error) => {
|
||||
notify.error(err, { title: strings.getShareableZipErrorTitle(workpad.name) });
|
||||
});
|
||||
return;
|
||||
default:
|
||||
throw new Error(strings.getUnknownExportErrorMessage(type));
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
)(Component);
|
|
@ -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 React, { FC } from 'react';
|
||||
import { EuiText, EuiSpacer, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { ComponentStrings } from '../../../../../i18n';
|
||||
|
||||
import { OnDownloadFn } from './share_website_flyout';
|
||||
|
||||
const { ShareWebsiteRuntimeStep: strings } = ComponentStrings;
|
||||
|
||||
export const RuntimeStep: FC<{ onDownload: OnDownloadFn }> = ({ onDownload }) => (
|
||||
<EuiText size="s">
|
||||
<p>{strings.getStepDescription()}</p>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton
|
||||
onClick={() => {
|
||||
onDownload('shareRuntime');
|
||||
}}
|
||||
size="s"
|
||||
>
|
||||
{strings.getDownloadLabel()}
|
||||
</EuiButton>
|
||||
</EuiText>
|
||||
);
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiCallOut,
|
||||
EuiSteps,
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiFlyoutBody,
|
||||
EuiTitle,
|
||||
EuiLink,
|
||||
EuiCode,
|
||||
EuiBetaBadge,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { ComponentStrings, ZIP, CANVAS, HTML } from '../../../../../i18n';
|
||||
import { OnCloseFn } from '../workpad_export';
|
||||
import { WorkpadStep } from './workpad_step';
|
||||
import { RuntimeStep } from './runtime_step';
|
||||
import { SnippetsStep } from './snippets_step';
|
||||
|
||||
const { ShareWebsiteFlyout: strings } = ComponentStrings;
|
||||
|
||||
export type OnDownloadFn = (type: 'share' | 'shareRuntime' | 'shareZip') => void;
|
||||
export type OnCopyFn = () => void;
|
||||
|
||||
export interface Props {
|
||||
onCopy: OnCopyFn;
|
||||
onDownload: OnDownloadFn;
|
||||
onClose: OnCloseFn;
|
||||
unsupportedRenderers?: string[];
|
||||
}
|
||||
|
||||
const steps = (onDownload: OnDownloadFn, onCopy: OnCopyFn) => [
|
||||
{
|
||||
title: strings.getWorkpadStepTitle(),
|
||||
children: <WorkpadStep {...{ onDownload }} />,
|
||||
},
|
||||
{
|
||||
title: strings.getRuntimeStepTitle(),
|
||||
children: <RuntimeStep {...{ onDownload }} />,
|
||||
},
|
||||
{
|
||||
title: strings.getSnippentsStepTitle(),
|
||||
children: <SnippetsStep {...{ onCopy }} />,
|
||||
},
|
||||
];
|
||||
|
||||
export const ShareWebsiteFlyout: FC<Props> = ({
|
||||
onCopy,
|
||||
onDownload,
|
||||
onClose,
|
||||
unsupportedRenderers,
|
||||
}) => {
|
||||
const link = (
|
||||
<EuiLink
|
||||
style={{ textDecoration: 'underline' }}
|
||||
onClick={() => {
|
||||
onDownload('shareZip');
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.canvas.shareWebsiteFlyout.zipDownloadLinkLabel"
|
||||
defaultMessage="download an example {ZIP} file"
|
||||
values={{ ZIP }}
|
||||
/>
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
const title = (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="xpack.canvas.shareWebsiteFlyout.flyoutCalloutDescription"
|
||||
defaultMessage="To try sharing, you can {link} containing this workpad, the {CANVAS} Shareable Workpad runtime, and a sample {HTML} file."
|
||||
values={{
|
||||
CANVAS,
|
||||
HTML,
|
||||
link,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
let warningText = null;
|
||||
|
||||
if (unsupportedRenderers && unsupportedRenderers.length > 0) {
|
||||
const warning = [
|
||||
<EuiText size="s" key="text">
|
||||
<span>{strings.getUnsupportedRendererWarning()}</span>
|
||||
{unsupportedRenderers.map((fn, index) => [
|
||||
<EuiCode key={`item-${index}`}>{fn}</EuiCode>,
|
||||
index < unsupportedRenderers.length - 1 ? ', ' : '',
|
||||
])}
|
||||
</EuiText>,
|
||||
<EuiSpacer size="xs" key="spacer" />,
|
||||
];
|
||||
warningText = [
|
||||
<EuiCallOut title={warning} color="warning" size="s" iconType="alert" key="callout" />,
|
||||
<EuiSpacer key="spacer" />,
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={() => onClose('share')} maxWidth>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<h2 id="flyoutTitle">
|
||||
<EuiFlexItem grow={false}>{strings.getTitle()}</EuiFlexItem>
|
||||
</h2>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge label="Beta" color="accent" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiText size="s">
|
||||
<p>{strings.getStepsDescription()}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut size="s" title={title} iconType="iInCircle" />
|
||||
<EuiSpacer />
|
||||
{warningText}
|
||||
<EuiSteps steps={steps(onDownload, onCopy)} />
|
||||
</EuiFlyoutBody>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import {
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiCode,
|
||||
EuiCodeBlock,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ComponentStrings } from '../../../../../i18n';
|
||||
|
||||
import { Clipboard } from '../../../clipboard';
|
||||
import { OnCopyFn } from './share_website_flyout';
|
||||
|
||||
const { ShareWebsiteSnippetsStep: strings } = ComponentStrings;
|
||||
|
||||
const HTML = `<!-- ${strings.getIncludeRuntimeLabel()} -->
|
||||
<script src="kbn_canvas.js"></script>
|
||||
|
||||
<!-- ${strings.getPlaceholderLabel()} -->
|
||||
<div kbn-canvas-shareable="canvas" kbn-canvas-url="workpad.json" />
|
||||
|
||||
<!-- ${strings.getCallRuntimeLabel()} -->
|
||||
<script type="text/javascript">
|
||||
KbnCanvas.share();
|
||||
</script>`;
|
||||
|
||||
export const SnippetsStep: FC<{ onCopy: OnCopyFn }> = ({ onCopy }) => (
|
||||
<div>
|
||||
<EuiText size="s">
|
||||
<p>{strings.getSnippetsStepDescription()}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<Clipboard content={HTML} onCopy={onCopy}>
|
||||
<EuiCodeBlock
|
||||
className="canvasWorkpadExport__reportingConfig"
|
||||
paddingSize="s"
|
||||
fontSize="s"
|
||||
language="html"
|
||||
>
|
||||
{HTML}
|
||||
</EuiCodeBlock>
|
||||
</Clipboard>
|
||||
<EuiSpacer />
|
||||
<EuiText>
|
||||
<h4>{strings.getParametersTitle()}</h4>
|
||||
<p>{strings.getParametersDescription()}</p>
|
||||
</EuiText>
|
||||
<EuiHorizontalRule />
|
||||
<EuiDescriptionList>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-shareable="canvas"</EuiCode> ({strings.getRequiredLabel()})
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getShareableParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-url</EuiCode> ({strings.getRequiredLabel()})
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getUrlParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-height</EuiCode>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getHeightParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-width</EuiCode>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getWidthParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-page</EuiCode>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getPageParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-autoplay</EuiCode>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getAutoplayParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-interval</EuiCode>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getIntervalParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiCode>kbn-canvas-toolbar</EuiCode>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{strings.getToolbarParameterDescription()}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
</div>
|
||||
);
|
|
@ -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 React, { FC } from 'react';
|
||||
import { EuiText, EuiSpacer, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { ComponentStrings } from '../../../../../i18n';
|
||||
|
||||
import { OnDownloadFn } from './share_website_flyout';
|
||||
|
||||
const { ShareWebsiteWorkpadStep: strings } = ComponentStrings;
|
||||
|
||||
export const WorkpadStep: FC<{ onDownload: OnDownloadFn }> = ({ onDownload }) => (
|
||||
<EuiText size="s">
|
||||
<p>{strings.getStepDescription()}</p>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButton
|
||||
onClick={() => {
|
||||
onDownload('share');
|
||||
}}
|
||||
size="s"
|
||||
>
|
||||
{strings.getDownloadLabel()}
|
||||
</EuiButton>
|
||||
</EuiText>
|
||||
);
|
|
@ -15,10 +15,15 @@ import { getReportingBrowserType } from '../../../state/selectors/app';
|
|||
import { notify } from '../../../lib/notify';
|
||||
import { getWindow } from '../../../lib/get_window';
|
||||
// @ts-ignore Untyped local
|
||||
import { downloadWorkpad } from '../../../lib/download_workpad';
|
||||
import {
|
||||
downloadWorkpad,
|
||||
// @ts-ignore Untyped local
|
||||
} from '../../../lib/download_workpad';
|
||||
import { WorkpadExport as Component, Props as ComponentProps } from './workpad_export';
|
||||
import { getPdfUrl, createPdf } from './utils';
|
||||
import { State, CanvasWorkpad } from '../../../../types';
|
||||
// @ts-ignore Untyped local.
|
||||
import { fetch, arrayBufferFetch } from '../../../../common/lib/fetch';
|
||||
|
||||
import { ComponentStrings } from '../../../../i18n';
|
||||
const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings;
|
||||
|
@ -88,7 +93,7 @@ export const WorkpadExport = compose<ComponentProps, {}>(
|
|||
});
|
||||
case 'json':
|
||||
downloadWorkpad(workpad.id);
|
||||
break;
|
||||
return;
|
||||
default:
|
||||
throw new Error(strings.getUnknownExportErrorMessage(type));
|
||||
}
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent, MouseEvent } from 'react';
|
||||
import React, { FunctionComponent, useState, MouseEvent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiButtonIcon, EuiContextMenu, EuiIcon } from '@elastic/eui';
|
||||
// @ts-ignore Untyped local
|
||||
import { Popover } from '../../popover';
|
||||
import { DisabledPanel } from './disabled_panel';
|
||||
import { PDFPanel } from './pdf_panel';
|
||||
import { ShareWebsiteFlyout } from './flyout';
|
||||
|
||||
import { ComponentStrings } from '../../../../i18n';
|
||||
const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings;
|
||||
|
@ -20,9 +21,11 @@ type ClosePopoverFn = () => void;
|
|||
type CopyTypes = 'pdf' | 'reportingConfig';
|
||||
type ExportTypes = 'pdf' | 'json';
|
||||
type ExportUrlTypes = 'pdf';
|
||||
type CloseTypes = 'share';
|
||||
|
||||
export type OnCopyFn = (type: CopyTypes) => void;
|
||||
export type OnExportFn = (type: ExportTypes) => void;
|
||||
export type OnCloseFn = (type: CloseTypes) => void;
|
||||
export type GetExportUrlFn = (type: ExportUrlTypes) => string;
|
||||
|
||||
export interface Props {
|
||||
|
@ -45,6 +48,12 @@ export const WorkpadExport: FunctionComponent<Props> = ({
|
|||
onExport,
|
||||
getExportUrl,
|
||||
}) => {
|
||||
const [showFlyout, setShowFlyout] = useState(false);
|
||||
|
||||
const onClose = () => {
|
||||
setShowFlyout(false);
|
||||
};
|
||||
|
||||
// TODO: Fix all of this magic from EUI; this code is boilerplate from
|
||||
// EUI examples and isn't easily typed.
|
||||
const flattenPanelTree = (tree: any, array: any[] = []) => {
|
||||
|
@ -109,6 +118,14 @@ export const WorkpadExport: FunctionComponent<Props> = ({
|
|||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: strings.getShareWebsiteTitle(),
|
||||
icon: <EuiIcon type="globe" size="m" />,
|
||||
onClick: () => {
|
||||
setShowFlyout(true);
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -120,17 +137,25 @@ export const WorkpadExport: FunctionComponent<Props> = ({
|
|||
/>
|
||||
);
|
||||
|
||||
const flyout = showFlyout ? <ShareWebsiteFlyout onClose={onClose} /> : null;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
button={exportControl}
|
||||
panelPaddingSize="none"
|
||||
tooltip={strings.getShareWorkpadMessage()}
|
||||
tooltipPosition="bottom"
|
||||
>
|
||||
{({ closePopover }: { closePopover: ClosePopoverFn }) => (
|
||||
<EuiContextMenu initialPanelId={0} panels={flattenPanelTree(getPanelTree(closePopover))} />
|
||||
)}
|
||||
</Popover>
|
||||
<div>
|
||||
<Popover
|
||||
button={exportControl}
|
||||
panelPaddingSize="none"
|
||||
tooltip={strings.getShareWorkpadMessage()}
|
||||
tooltipPosition="bottom"
|
||||
>
|
||||
{({ closePopover }: { closePopover: ClosePopoverFn }) => (
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={flattenPanelTree(getPanelTree(closePopover))}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
{flyout}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -8,12 +8,11 @@ import { shallowEqual } from 'recompose';
|
|||
import { getNodes, getSelectedPage } from '../../state/selectors/workpad';
|
||||
import { addElement, removeElements, setMultiplePositions } from '../../state/actions/elements';
|
||||
import { selectToplevelNodes } from '../../state/actions/transient';
|
||||
import { matrixToAngle } from '../../lib/aeroelastic/matrix';
|
||||
import { arrayToMap, flatten, identity } from '../../lib/aeroelastic/functional';
|
||||
import { getLocalTransformMatrix } from '../../lib/aeroelastic/layout_functions';
|
||||
import { isGroupId, elementToShape } from './positioning_utils';
|
||||
|
||||
export * from './positioning_utils';
|
||||
import { matrixToAngle } from '../../lib/aeroelastic/matrix';
|
||||
import { isGroupId, elementToShape } from './utils';
|
||||
export * from './utils';
|
||||
|
||||
const shapeToElement = shape => ({
|
||||
left: shape.transformMatrix[12] - shape.a,
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { multiply, rotateZ, translate } from '../../lib/aeroelastic/matrix';
|
||||
|
||||
export const isGroupId = id => id.startsWith('group');
|
||||
|
||||
const headerData = id =>
|
||||
isGroupId(id)
|
||||
? { id, type: 'group', subtype: 'persistentGroup' }
|
||||
: { id, type: 'rectangleElement', subtype: '' };
|
||||
|
||||
const transformData = ({ top, left, width, height, angle }, z) =>
|
||||
multiply(
|
||||
translate(left + width / 2, top + height / 2, z), // painter's algo: latest item (highest z) goes to top
|
||||
rotateZ((-angle / 180) * Math.PI) // minus angle as transform:matrix3d uses a left-handed coordinate system
|
||||
);
|
||||
|
||||
/**
|
||||
* elementToShape
|
||||
*
|
||||
* converts a `kibana-canvas` element to an `aeroelastic` shape.
|
||||
*
|
||||
* Shape: the layout algorithms need to deal with objects through their geometric properties, excluding other aspects,
|
||||
* such as what's inside the element, eg. image or scatter plot. This representation is, at its core, a transform matrix
|
||||
* that establishes a new local coordinate system https://drafts.csswg.org/css-transforms/#local-coordinate-system plus a
|
||||
* size descriptor. There are two versions of the transform matrix:
|
||||
* - `transformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#current-transformation-matrix
|
||||
* - `localTransformMatrix` is analogous to the SVG https://drafts.csswg.org/css-transforms/#transformation-matrix
|
||||
*
|
||||
* Element: it also needs to represent the geometry, primarily because of the need to persist it in `redux` and on the
|
||||
* server, and to accept such data from the server. The redux and server representations will need to change as more general
|
||||
* projections such as 3D are added. The element also needs to maintain its content, such as an image or a plot.
|
||||
*
|
||||
* While all elements on the current page also exist as shapes, there are shapes that are not elements: annotations.
|
||||
* For example, `rotation_handle`, `border_resize_handle` and `border_connection` are modeled as shapes by the layout
|
||||
* library, simply for generality.
|
||||
*/
|
||||
export const elementToShape = ({ id, position }, z) => ({
|
||||
...headerData(id),
|
||||
parent: (position && position.parent) || null,
|
||||
transformMatrix: transformData(position, z),
|
||||
a: position.width / 2, // we currently specify half-width, half-height as it leads to
|
||||
b: position.height / 2, // more regular math (like ellipsis radii rather than diameters)
|
||||
});
|
||||
|
||||
const simplePosition = ({ id, position, filter }, z) => ({
|
||||
...headerData(id),
|
||||
width: position.width,
|
||||
height: position.height,
|
||||
transformMatrix: transformData(position, z),
|
||||
filter,
|
||||
});
|
||||
|
||||
export const simplePositioning = ({ elements }) => ({ elements: elements.map(simplePosition) });
|
|
@ -4,11 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import fileSaver from 'file-saver';
|
||||
import chrome from 'ui/chrome';
|
||||
import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../common/lib/constants';
|
||||
import { ErrorStrings } from '../../i18n';
|
||||
// @ts-ignore untyped local
|
||||
import { notify } from './notify';
|
||||
// @ts-ignore untyped local
|
||||
import * as workpadService from './workpad_service';
|
||||
import { CanvasRenderedWorkpad } from '../../shareable_runtime/types';
|
||||
|
||||
const { downloadWorkpad: strings } = ErrorStrings;
|
||||
|
||||
|
@ -21,3 +24,35 @@ export const downloadWorkpad = async (workpadId: string) => {
|
|||
notify.error(err, { title: strings.getDownloadFailureErrorMessage() });
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadRenderedWorkpad = async (renderedWorkpad: CanvasRenderedWorkpad) => {
|
||||
try {
|
||||
const jsonBlob = new Blob([JSON.stringify(renderedWorkpad)], { type: 'application/json' });
|
||||
fileSaver.saveAs(
|
||||
jsonBlob,
|
||||
`canvas-embed-workpad-${renderedWorkpad.name}-${renderedWorkpad.id}.json`
|
||||
);
|
||||
} catch (err) {
|
||||
notify.error(err, { title: strings.getDownloadRenderedWorkpadFailureErrorMessage() });
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadRuntime = async () => {
|
||||
try {
|
||||
const basePath = chrome.getBasePath();
|
||||
const path = `${basePath}${API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD}`;
|
||||
window.open(path);
|
||||
return;
|
||||
} catch (err) {
|
||||
notify.error(err, { title: strings.getDownloadRuntimeFailureErrorMessage() });
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadZippedRuntime = async (data: any) => {
|
||||
try {
|
||||
const zip = new Blob([data], { type: 'octet/stream' });
|
||||
fileSaver.saveAs(zip, 'canvas-workpad-embed.zip');
|
||||
} catch (err) {
|
||||
notify.error(err, { title: strings.getDownloadZippedRuntimeFailureErrorMessage() });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -421,3 +421,56 @@ export function getRefreshInterval(state: State): number {
|
|||
export function getAutoplay(state: State): State['transient']['autoplay'] {
|
||||
return get(state, 'transient.autoplay');
|
||||
}
|
||||
|
||||
export function getRenderedWorkpad(state: State) {
|
||||
const currentPages = getPages(state);
|
||||
const args = state.transient.resolvedArgs;
|
||||
const renderedPages = currentPages.map(page => {
|
||||
const { elements, ...rest } = page;
|
||||
return {
|
||||
...rest,
|
||||
elements: elements.map(element => {
|
||||
const { id, position } = element;
|
||||
const arg = args[id];
|
||||
if (!arg) {
|
||||
return null;
|
||||
}
|
||||
const { expressionRenderable } = arg;
|
||||
|
||||
return { id, position, expressionRenderable };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const workpad = getWorkpad(state);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { pages, ...rest } = workpad;
|
||||
|
||||
return {
|
||||
pages: renderedPages,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
||||
export function getRenderedWorkpadExpressions(state: State) {
|
||||
const workpad = getRenderedWorkpad(state);
|
||||
const { pages } = workpad;
|
||||
const expressions: string[] = [];
|
||||
|
||||
pages.forEach(page =>
|
||||
page.elements.forEach(element => {
|
||||
if (element && element.expressionRenderable) {
|
||||
const { value } = element.expressionRenderable;
|
||||
if (value) {
|
||||
const { as } = value;
|
||||
if (!expressions.includes(as)) {
|
||||
expressions.push(as);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return expressions;
|
||||
}
|
||||
|
|
|
@ -43,26 +43,24 @@ run(
|
|||
'--coverageDirectory', // Output to canvas/coverage
|
||||
'legacy/plugins/canvas/coverage',
|
||||
];
|
||||
}
|
||||
// Mitigation for https://github.com/facebook/jest/issues/7267
|
||||
if (all || storybook) {
|
||||
options = options.concat(['--no-cache', '--no-watchman']);
|
||||
}
|
||||
|
||||
if (all) {
|
||||
log.info('Running all available tests. This will take a while...');
|
||||
} else if (storybook) {
|
||||
path = 'legacy/plugins/canvas/.storybook';
|
||||
log.info('Running Storybook Snapshot tests...');
|
||||
} else {
|
||||
// Mitigation for https://github.com/facebook/jest/issues/7267
|
||||
if (all || storybook || update) {
|
||||
options = options.concat(['--no-cache', '--no-watchman']);
|
||||
}
|
||||
log.info('Running tests. This does not include Storybook Snapshots...');
|
||||
}
|
||||
|
||||
if (all) {
|
||||
log.info('Running all available tests. This will take a while...');
|
||||
} else if (storybook || update) {
|
||||
path = 'legacy/plugins/canvas/.storybook';
|
||||
|
||||
if (update) {
|
||||
log.info('Updating Storybook Snapshot tests...');
|
||||
options.push('-u');
|
||||
} else {
|
||||
log.info('Running Storybook Snapshot tests...');
|
||||
}
|
||||
} else {
|
||||
log.info('Running tests. This does not include Storybook Snapshots...');
|
||||
}
|
||||
if (update) {
|
||||
log.info('Updating any Jest snapshots...');
|
||||
options.push('-u');
|
||||
}
|
||||
|
||||
runXPackScript('jest', [path].concat(options));
|
||||
|
|
114
x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js
Normal file
114
x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { pipeline } = require('stream');
|
||||
const { promisify } = require('util');
|
||||
|
||||
const del = require('del');
|
||||
const { run } = require('@kbn/dev-utils');
|
||||
const execa = require('execa');
|
||||
|
||||
const asyncPipeline = promisify(pipeline);
|
||||
|
||||
const {
|
||||
SHAREABLE_RUNTIME_SRC: RUNTIME_SRC,
|
||||
KIBANA_ROOT,
|
||||
STATS_OUTPUT,
|
||||
SHAREABLE_RUNTIME_FILE: RUNTIME_FILE,
|
||||
} = require('../shareable_runtime/constants');
|
||||
|
||||
run(
|
||||
async ({ log, flags }) => {
|
||||
const options = {
|
||||
cwd: KIBANA_ROOT,
|
||||
stdio: ['ignore', 'inherit', 'inherit'],
|
||||
buffer: false,
|
||||
};
|
||||
|
||||
log.info('pre-req: Ensuring Kibana SCSS is built.');
|
||||
// Ensure SASS has been built completely before building the runtime.
|
||||
execa.sync(process.execPath, ['scripts/build_sass'], {
|
||||
...options,
|
||||
});
|
||||
|
||||
const webpackConfig = path.resolve(RUNTIME_SRC, 'webpack.config.js');
|
||||
|
||||
const clean = () => {
|
||||
log.info('Deleting previous build.');
|
||||
del.sync([RUNTIME_FILE], { force: true });
|
||||
};
|
||||
|
||||
if (flags.clean) {
|
||||
clean();
|
||||
}
|
||||
|
||||
const env = {};
|
||||
|
||||
if (!flags.dev) {
|
||||
env.NODE_ENV = 'production';
|
||||
}
|
||||
|
||||
if (flags.run) {
|
||||
log.info('Starting Webpack Dev Server...');
|
||||
execa.sync(
|
||||
'yarn',
|
||||
[
|
||||
'webpack-dev-server',
|
||||
'--config',
|
||||
webpackConfig,
|
||||
'--progress',
|
||||
'--hide-modules',
|
||||
'--display-entrypoints',
|
||||
'false',
|
||||
'--content-base',
|
||||
RUNTIME_SRC,
|
||||
],
|
||||
options
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags.stats) {
|
||||
log.info('Writing Webpack stats...');
|
||||
const output = execa(
|
||||
require.resolve('webpack/bin/webpack'),
|
||||
['--config', webpackConfig, '--profile', '--json'],
|
||||
{
|
||||
...options,
|
||||
env,
|
||||
stdio: ['ignore', 'pipe', 'inherit'],
|
||||
}
|
||||
);
|
||||
await asyncPipeline(output.stdout, fs.createWriteStream(STATS_OUTPUT));
|
||||
log.success('...output written to', STATS_OUTPUT);
|
||||
return;
|
||||
}
|
||||
|
||||
clean();
|
||||
log.info('Building Canvas Shareable Workpad Runtime...');
|
||||
execa.sync('yarn', ['webpack', '--config', webpackConfig, '--hide-modules', '--progress'], {
|
||||
...options,
|
||||
env,
|
||||
});
|
||||
log.success('...runtime built!');
|
||||
},
|
||||
{
|
||||
description: `
|
||||
Build script for the Canvas Shareable Workpad Runtime.
|
||||
`,
|
||||
flags: {
|
||||
boolean: ['run', 'clean', 'help', 'stats', 'dev'],
|
||||
help: `
|
||||
--run Run a server with the runtime
|
||||
--dev Build and/or create stats in development mode.
|
||||
--stats Output Webpack statistics to a stats.json file.
|
||||
--clean Delete the existing build
|
||||
`,
|
||||
},
|
||||
}
|
||||
);
|
|
@ -7,9 +7,11 @@
|
|||
import { workpad } from './workpad';
|
||||
import { esFields } from './es_fields';
|
||||
import { customElements } from './custom_elements';
|
||||
import { shareableWorkpads } from './shareables';
|
||||
|
||||
export function routes(server) {
|
||||
customElements(server);
|
||||
esFields(server);
|
||||
workpad(server);
|
||||
shareableWorkpads(server);
|
||||
}
|
||||
|
|
62
x-pack/legacy/plugins/canvas/server/routes/shareables.ts
Normal file
62
x-pack/legacy/plugins/canvas/server/routes/shareables.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { Server } from 'hapi';
|
||||
import archiver from 'archiver';
|
||||
|
||||
import {
|
||||
API_ROUTE_SHAREABLE_RUNTIME,
|
||||
API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD,
|
||||
API_ROUTE_SHAREABLE_ZIP,
|
||||
} from '../../common/lib/constants';
|
||||
|
||||
import {
|
||||
SHAREABLE_RUNTIME_FILE,
|
||||
SHAREABLE_RUNTIME_NAME,
|
||||
SHAREABLE_RUNTIME_SRC,
|
||||
} from '../../shareable_runtime/constants';
|
||||
|
||||
export function shareableWorkpads(server: Server) {
|
||||
// get runtime
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: API_ROUTE_SHAREABLE_RUNTIME,
|
||||
handler: {
|
||||
file: SHAREABLE_RUNTIME_FILE,
|
||||
},
|
||||
});
|
||||
|
||||
// download runtime
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD,
|
||||
handler(_request, handler) {
|
||||
// @ts-ignore No type for inert Hapi handler
|
||||
const file = handler.file(SHAREABLE_RUNTIME_FILE);
|
||||
file.type('application/octet-stream');
|
||||
return file;
|
||||
},
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: API_ROUTE_SHAREABLE_ZIP,
|
||||
handler(request, handler) {
|
||||
const workpad = request.payload;
|
||||
|
||||
const archive = archiver('zip');
|
||||
archive.append(JSON.stringify(workpad), { name: 'workpad.json' });
|
||||
archive.file(`${SHAREABLE_RUNTIME_SRC}/template.html`, { name: 'index.html' });
|
||||
archive.file(SHAREABLE_RUNTIME_FILE, { name: `${SHAREABLE_RUNTIME_NAME}.js` });
|
||||
|
||||
const response = handler.response(archive);
|
||||
response.header('content-type', 'application/zip');
|
||||
archive.finalize();
|
||||
|
||||
return response;
|
||||
},
|
||||
});
|
||||
}
|
276
x-pack/legacy/plugins/canvas/shareable_runtime/README.md
Normal file
276
x-pack/legacy/plugins/canvas/shareable_runtime/README.md
Normal file
|
@ -0,0 +1,276 @@
|
|||
# Canvas Shareable Workpads
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Using the Runtime](#using-the-runtime)
|
||||
- [Assumptions](#assumptions)
|
||||
- [Restrictions](#restrictions)
|
||||
- [JS](#js)
|
||||
- [HTML](#html)
|
||||
- [Options](#options)
|
||||
- [Testing](#testing)
|
||||
- [Download a ZIP from Canvas](#download-a-zip-from-canvas)
|
||||
- [Test the Runtime Directly from Webpack](#test-the-runtime-directly-from-webpack)
|
||||
- [Run the Canvas Storybook](#run-the-canvas-storybook)
|
||||
- [Run the Jest Tests](#run-the-jest-tests)
|
||||
- [Gathering Test Coverage](#gathering-test-coverage)
|
||||
- [Building](#building)
|
||||
- [Build Options](#build-options)
|
||||
- [Development](#development)
|
||||
- [Prerequisite](#prerequisite)
|
||||
- [Webpack Dev Server](#webpack-dev-server)
|
||||
- [Gathering Statistics](#gathering-statistics)
|
||||
- [Architecture](#architecture)
|
||||
- [The Build](#the-build)
|
||||
- [Supported Expressions](#supported-expressions)
|
||||
- [Expression Interpreter](#expression-interpreter)
|
||||
- [Build Size](#build-size)
|
||||
- [The App](#the-app)
|
||||
- [App State](#app-state)
|
||||
- [CSS](#css)
|
||||
|
||||
## Introduction
|
||||
|
||||
The Canvas Shareable Runtime is designed to render Shareable Canvas Workpads outside of Kibana in a different website or application. It uses the intermediate, "transient" state of a workpad, which is a JSON-blob state after element expressions are initially evaluated against their data sources, but before the elements are rendered to the screen. This "transient" state, therefore, has no dependency or access to ES/Kibana data, making it lightweight and portable.
|
||||
|
||||
This directory contains the code necessary to build and test this runtime.
|
||||
|
||||
## Quick Start
|
||||
|
||||
- Load a workpad in Canvas.
|
||||
- Click "Export" -> "Share on a website" -> "download a ZIP file"
|
||||
- Extract and change to the extracted directory.
|
||||
- On your local machine:
|
||||
- Start a web server, like: `python -m SimpleHTTPServer 9001`
|
||||
- Open a web browser to `http://localhost:9001`
|
||||
- On a remote webserver:
|
||||
- Add `kbn_canvas.js` and your Shared Workpad file to your web page:
|
||||
```
|
||||
<script src="kbn_canvas.js"></script>
|
||||
```
|
||||
- Add the HTML snippet to your webpage:
|
||||
```
|
||||
<div kbn-canvas-shareable="canvas" kbn-canvas-url="[WORKPAD URL]" />
|
||||
```
|
||||
- Execute the JS method:
|
||||
```
|
||||
<script type="text/javascript">
|
||||
KbnCanvas.share();
|
||||
</script>
|
||||
```
|
||||
|
||||
## Using the Runtime
|
||||
|
||||
### Assumptions
|
||||
|
||||
- The runtime is added to a web page using a standard `<script>` tag.
|
||||
- A Shared Workpad JSON file (see: [Testing](#Testing)) is available via some known URL.
|
||||
|
||||
### Restrictions
|
||||
|
||||
Not all elements from a workpad may render in the runtime. See [Supported Expressions](#supported-expressions) for more details.
|
||||
|
||||
### JS
|
||||
|
||||
The runtime is a global library with `KbnCanvas` as the namespace. When executed, the `share` method will interpret any and all nodes that match the API. This function can be called from anywhere, in a script block at the bottom of the page, or after any other initialization.
|
||||
|
||||
```html
|
||||
<script type="text/javascript">
|
||||
KbnCanvas.share();
|
||||
</script>
|
||||
```
|
||||
|
||||
### HTML
|
||||
|
||||
The Canvas Shareable Runtime will scan the DOM of a given web page looking for any element with `kbn-canvas-shareable="canvas"` as an attribute. This DOM node will be the host in which the workpad will be rendered. The node will also be sized and manipulated as necessary, but all other attributes, (such as `id`) will remain unaltered. A class name, `kbnCanvas`, will be _added_ to the DOM node.
|
||||
|
||||
> Note: Any content within this DOM node will be replaced.
|
||||
|
||||
Options to configure the runtime are included on the DOM node. The only required attribute is `kbn-canvas-url`, the URL from which the shared workpad can be loaded.
|
||||
|
||||
> Note: the workpad is loaded by `fetch`, therefore the runtime cannot be initialized on the local file system. Relative URLs are allowed.
|
||||
|
||||
Each attribute on the node that is correctly parsed will be removed. For example:
|
||||
|
||||
```html
|
||||
<!-- Markup added to the source file. -->
|
||||
<div kbn-canvas-shareable="canvas" kbn-canvas-height="400" kbn-canvas-url="workpad.json" />
|
||||
|
||||
<!-- Markup in the DOM after runtime processes it. -->
|
||||
<div class="kbnCanvas" />
|
||||
```
|
||||
|
||||
A sure sign that there was an error, or that an attribute was included that is not recognized, would be any attributes remaining:
|
||||
|
||||
```html
|
||||
<!-- Markup added to the source file. -->
|
||||
<div kbn-canvas-shareable="canvas" kbn-canvas-hieght="400" kbn-canvas-url="workpad.json" />
|
||||
|
||||
<!-- Markup in the DOM after runtime processes it. -->
|
||||
<div class="kbnCanvas" kbn-canvas-hieght="400" />
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
The [`api/shareable.tsx`]('./api/shareable') component file contains the base class with available options to configure the Shareable Workpad. Each of these would be prefixed with `kbn-canvas-`:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* The preferred height to scale the Shareable Canvas Workpad. If only `height` is
|
||||
* specified, `width` will be calculated by the workpad ratio. If both are
|
||||
* specified, the ratio will be overriden by an absolute size.
|
||||
*/
|
||||
height?: number;
|
||||
|
||||
/**
|
||||
* The preferred width to scale the Shareable Canvas Workpad. If only `width` is
|
||||
* specified, `height` will be calculated by the workpad ratio. If both are
|
||||
* specified, the ratio will be overriden by an absolute size.
|
||||
*/
|
||||
width?: number;
|
||||
|
||||
/**
|
||||
* The initial page to display.
|
||||
*/
|
||||
page?: number;
|
||||
|
||||
/**
|
||||
* Should the runtime automatically move through the pages of the workpad?
|
||||
* @default false
|
||||
*/
|
||||
autoplay?: boolean;
|
||||
|
||||
/**
|
||||
* The interval upon which the pages will advance in time format, (e.g. 2s, 1m)
|
||||
* @default '5s'
|
||||
* */
|
||||
interval?: string;
|
||||
|
||||
/**
|
||||
* Should the toolbar be hidden?
|
||||
* @default false
|
||||
*/
|
||||
toolbar?: boolean;
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
You can test this functionality in a number of ways. The easiest would be:
|
||||
|
||||
### Download a ZIP from Canvas
|
||||
|
||||
- Load a workpad in Canvas.
|
||||
- Click "Export" -> "Share on a website" -> "download a ZIP file"
|
||||
- Extract and change to the extracted directory.
|
||||
- Start a web server, like: `python -m SimpleHTTPServer 9001`
|
||||
- Open a web browser to `http://localhost:9001`
|
||||
|
||||
### Test the Runtime Directly from Webpack
|
||||
|
||||
- Load a workpad in Canvas.
|
||||
- Click "Export" -> "Share on a website" -> "Download Workpad"
|
||||
- Copy the workpad to `canvas/shareable_runtime/test`.
|
||||
- Edit `canvas/shareable_runtime/index.html` to include your workpad.
|
||||
- From `/canvas`, run `node scripts/shareable_runtime --run`
|
||||
- Open a web browser to `http://localhost:8080`
|
||||
|
||||
### Run the Canvas Storybook
|
||||
|
||||
From `/canvas`: `node scripts/storybook`
|
||||
|
||||
### Run the Jest Tests
|
||||
|
||||
The Jest tests utilize Enzyme to test interactions within the runtime, as well.
|
||||
|
||||
From `/canvas`: `node scripts/jest --path shareable_runtime`
|
||||
|
||||
#### Gathering Test Coverage
|
||||
|
||||
From `/canvas`: `node scripts/jest --path shareable_runtime --coverage`
|
||||
|
||||
## Building
|
||||
|
||||
Run `node scripts/shareable_runtime`. The runtime will be built and stored `shareable_runtime/build`.
|
||||
|
||||
### Build Options
|
||||
|
||||
By default, `scripts/shareable_runtime` will build a production-ready JS library. This takes a bit longer and produces a single file.
|
||||
|
||||
There are a number of options for the build script:
|
||||
|
||||
- `--dev` - allow Webpack to chunk the runtime into several files. This is helpful when developing the runtime itself.
|
||||
- `--run` - run the Webpack Dev Server to develop and test the runtime. It will use HMR to incorporate changes.
|
||||
- `--clean` - clean the runtime from the build directory.
|
||||
- `--stats` - output Webpack statistics for the runtime.
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisite
|
||||
|
||||
Before testing or running this PR locally, you **must** run `node scripts/runtime` from `/canvas` _after_ `yarn kbn bootstrap` and _before_ starting Kibana. It is only built automatically when Kibana is built to avoid slowing down other development activities.
|
||||
|
||||
### Webpack Dev Server
|
||||
|
||||
To start the `webpack-dev-server` and test a workpad, simply run:
|
||||
|
||||
`/canvas`: `node scripts/shareable_runtime --dev --run`
|
||||
|
||||
A browser window should automatically open. If not, open a browser to [`http://localhost:8080/`](http://localhost:8080).
|
||||
|
||||
The `index.html` file contains a call to the `CanvasShareable` runtime. Currently, you can share by object or by url:
|
||||
|
||||
```html
|
||||
<script src="kbn_canvas.js"></script>
|
||||
...
|
||||
<div kbn-canvas-shareable="canvas" kbn-canvas-height="400" kbn-canvas-url="workpad.json"></div>
|
||||
<script type="text/javascript">
|
||||
KbnCanvas.share();
|
||||
</script>
|
||||
```
|
||||
|
||||
There are three workpads available, in `test/workpads`:
|
||||
|
||||
- `hello.json` - A simple 'Hello, Canvas' workpad.
|
||||
- `austin.json` - A workpad from an Elastic{ON} talk in Austin, TX.
|
||||
- `test.json` - A couple of pages with customized CSS animations and charts.
|
||||
|
||||
### Gathering Statistics
|
||||
|
||||
Webpack will output a `stats.json` file for analysis. This allows us to know how large the runtime is, where the largest dependencies are coming from, and how we might prune down its size. Two popular sites are:
|
||||
|
||||
- Official Webpack Analysis tool: http://webpack.github.io/analyse/
|
||||
- Webpack Visualizer: https://chrisbateman.github.io/webpack-visualizer/
|
||||
|
||||
## Architecture
|
||||
|
||||
The Shareable Runtime is an independently-built artifact for use outside of Kibana. It consists of two parts: the Build and the App.
|
||||
|
||||
### The Build
|
||||
|
||||
A custom Webpack build is used to incorporate code from Canvas, Kibana and EUI into a single file for distribution. This code interprets the shared JSON workpad file and renders the pages of elements within the area provided.
|
||||
|
||||
#### Supported Expressions
|
||||
|
||||
Because Shareable Workpads are not connected to any data source in Kibana or otherwise, the runtime simply renders the transient state of the workpad at the time it was shared from within Canvas. So elements that are used to manipulate data, (e.g. filtering controls like `time_filter` or `dropdown_filter`) are not included in the runtime. This lowers the runtime size. Any element that uses an excluded renderer will render nothing in their place. Users are warned within Canvas as they download the Shared Workpad if their workpad contains any of these non-rendered controls.
|
||||
|
||||
> Note: Since the runtime is statically built with the Kibana release, renderers provided by plugins are not supported. Functions that use standard renderers, provided they are not data-manipulating, will still work as expected.
|
||||
|
||||
#### Expression Interpreter
|
||||
|
||||
Kibana and Canvas use an interpreter to register expressions and then eventually evaluate them at run time. Most of the code within the interpreter is not needed for the Shareable Runtime. As a result, a bespoke interpreter is used instead.
|
||||
|
||||
#### Build Size
|
||||
|
||||
At the moment, the resulting library is relatively large, (5.6M). This is due to the bundling of dependencies like EUI. By trading off file size, we're able to keep the library contained without a need to download other external dependencies, (like React). We're working to reduce that size through further tree-shaking or compression.
|
||||
|
||||
### The App
|
||||
|
||||
The App refers to the user interface that is embedded on the consuming web page and displays the workpad.
|
||||
|
||||
#### App State
|
||||
|
||||
To minimize the distribution size, we opted to avoid as many libraries as possible in the UI. So while Canvas uses Redux to maintain state, we opted for a React Context + Hooks-based approach with a custom reducer. This code can be found in `shareable_runtime/context`.
|
||||
|
||||
#### CSS
|
||||
|
||||
All CSS in the runtime UI uses CSS Modules to sandbox and obfuscate class names. In addition, the Webpack build uses `postcss-prefix-selector` to prefix all public class names from Kibana and EUI with `.kbnCanvas`. As a result, all class names should be sandboxed and not interfere with the host page in any way.
|
|
@ -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 ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
|
||||
const renderers = [
|
||||
'debug',
|
||||
'error',
|
||||
'image',
|
||||
'repeatImage',
|
||||
'revealImage',
|
||||
'markdown',
|
||||
'metric',
|
||||
'pie',
|
||||
'plot',
|
||||
'progress',
|
||||
'shape',
|
||||
'table',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* Mock all of the render functions to return a `div` containing
|
||||
* a predictable string.
|
||||
*/
|
||||
export const renderFunctions = renderers.map(fn => () => ({
|
||||
name: fn,
|
||||
displayName: fn,
|
||||
help: fn,
|
||||
reuseDomNode: true,
|
||||
render: domNode => {
|
||||
ReactDOM.render(<div>{fn} mock</div>, domNode);
|
||||
},
|
||||
}));
|
61
x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap
generated
Normal file
61
x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap
generated
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { sharedWorkpads, tick } from '../../test';
|
||||
import { share } from '../shareable';
|
||||
|
||||
// Mock the renderers within this test.
|
||||
jest.mock('../../supported_renderers');
|
||||
|
||||
describe('Canvas Shareable Workpad API', () => {
|
||||
// Mock the AJAX load of the workpad.
|
||||
beforeEach(function() {
|
||||
// @ts-ignore Applying a global in Jest is alright.
|
||||
global.fetch = jest.fn().mockImplementation(() => {
|
||||
const p = new Promise((resolve, _reject) => {
|
||||
resolve({
|
||||
ok: true,
|
||||
json: () => {
|
||||
return sharedWorkpads.hello;
|
||||
},
|
||||
});
|
||||
});
|
||||
return p;
|
||||
});
|
||||
});
|
||||
|
||||
test('Placed successfully with default properties', async () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const wrapper = mount(<div kbn-canvas-shareable="canvas" kbn-canvas-url="workpad.json"></div>, {
|
||||
attachTo: container,
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
share();
|
||||
await tick();
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Placed successfully with height specified', async () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const wrapper = mount(
|
||||
<div
|
||||
kbn-canvas-shareable="canvas"
|
||||
kbn-canvas-height="350"
|
||||
kbn-canvas-url="workpad.json"
|
||||
></div>,
|
||||
{
|
||||
attachTo: container,
|
||||
}
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
share();
|
||||
await tick();
|
||||
expect(wrapper.html()).toMatch(
|
||||
/<div class=\"container\" style="height: 350px; width: 525px;\">/
|
||||
);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Placed successfully with width specified', async () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const wrapper = mount(
|
||||
<div
|
||||
kbn-canvas-shareable="canvas"
|
||||
kbn-canvas-width="400"
|
||||
kbn-canvas-url="workpad.json"
|
||||
></div>,
|
||||
{
|
||||
attachTo: container,
|
||||
}
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
share();
|
||||
await tick();
|
||||
expect(wrapper.html()).toMatch(
|
||||
/<div class=\"container\" style="height: 267px; width: 400px;\">/
|
||||
);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Placed successfully with width and height specified', async () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const wrapper = mount(
|
||||
<div
|
||||
kbn-canvas-shareable="canvas"
|
||||
kbn-canvas-width="350"
|
||||
kbn-canvas-height="350"
|
||||
kbn-canvas-url="workpad.json"
|
||||
></div>,
|
||||
{
|
||||
attachTo: container,
|
||||
}
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
share();
|
||||
await tick();
|
||||
expect(wrapper.html()).toMatch(
|
||||
/<div class=\"container\" style="height: 350px; width: 350px;\">/
|
||||
);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Placed successfully with page specified', async () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const wrapper = mount(
|
||||
<div kbn-canvas-shareable="canvas" kbn-canvas-page="0" kbn-canvas-url="workpad.json"></div>,
|
||||
{
|
||||
attachTo: container,
|
||||
}
|
||||
);
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
share();
|
||||
await tick();
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
10
x-pack/legacy/plugins/canvas/shareable_runtime/api/index.ts
Normal file
10
x-pack/legacy/plugins/canvas/shareable_runtime/api/index.ts
Normal file
|
@ -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.
|
||||
*/
|
||||
|
||||
import 'whatwg-fetch';
|
||||
import 'babel-polyfill';
|
||||
|
||||
export * from './shareable';
|
157
x-pack/legacy/plugins/canvas/shareable_runtime/api/shareable.tsx
Normal file
157
x-pack/legacy/plugins/canvas/shareable_runtime/api/shareable.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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 { render } from 'react-dom';
|
||||
import { App } from '../components/app';
|
||||
import { CanvasRenderedWorkpad } from '../types';
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
* The preferred height to scale the shared workpad. If only `height` is
|
||||
* specified, `width` will be calculated by the workpad ratio. If both are
|
||||
* specified, the ratio will be overriden by an absolute size.
|
||||
* @default The height provided by the workpad.
|
||||
*/
|
||||
height?: number;
|
||||
|
||||
/**
|
||||
* The preferred width to scale the shared workpad. If only `width` is
|
||||
* specified, `height` will be calculated by the workpad ratio. If both are
|
||||
* specified, the ratio will be overriden by an absolute size.
|
||||
* @default The width provided by the workpad.
|
||||
*/
|
||||
width?: number;
|
||||
|
||||
/**
|
||||
* The initial page to display.
|
||||
* @default The page provided by the workpad.
|
||||
*/
|
||||
page?: number;
|
||||
|
||||
/**
|
||||
* Should the runtime automatically move through the pages of the workpad?
|
||||
* @default false
|
||||
*/
|
||||
autoplay?: boolean;
|
||||
|
||||
/**
|
||||
* The interval upon which the pages will advance in time format, (e.g. 2s, 1m)
|
||||
* @default '5s'
|
||||
* */
|
||||
interval?: string;
|
||||
|
||||
/**
|
||||
* Should the toolbar be hidden?
|
||||
* @default false
|
||||
*/
|
||||
toolbar?: boolean;
|
||||
}
|
||||
|
||||
// All data attributes start with this prefix.
|
||||
const PREFIX = 'kbn-canvas';
|
||||
|
||||
// The identifying data attribute for all shareable workpads.
|
||||
const SHAREABLE = `${PREFIX}-shareable`;
|
||||
|
||||
// Valid option attributes, preceded by `PREFIX` in markup.
|
||||
const VALID_ATTRIBUTES = ['url', 'page', 'height', 'width', 'autoplay', 'interval', 'toolbar'];
|
||||
|
||||
// Collect and then remove valid data attributes.
|
||||
const getAttributes = (element: Element, attributes: string[]) => {
|
||||
const result: { [key: string]: string } = {};
|
||||
attributes.forEach(attribute => {
|
||||
const key = `${PREFIX}-${attribute}`;
|
||||
const value = element.getAttribute(key);
|
||||
|
||||
if (value) {
|
||||
result[attribute] = value;
|
||||
element.removeAttribute(key);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getWorkpad = async (url: string): Promise<CanvasRenderedWorkpad | null> => {
|
||||
const workpadResponse = await fetch(url);
|
||||
|
||||
if (workpadResponse.ok) {
|
||||
return await workpadResponse.json();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const updateArea = async (area: Element) => {
|
||||
const {
|
||||
url,
|
||||
page: pageAttr,
|
||||
height: heightAttr,
|
||||
width: widthAttr,
|
||||
autoplay,
|
||||
interval,
|
||||
toolbar,
|
||||
} = getAttributes(area, VALID_ATTRIBUTES);
|
||||
|
||||
if (url) {
|
||||
const workpad = await getWorkpad(url);
|
||||
|
||||
if (workpad) {
|
||||
const page = pageAttr ? parseInt(pageAttr, 10) : null;
|
||||
let height = heightAttr ? parseInt(heightAttr, 10) : null;
|
||||
let width = widthAttr ? parseInt(widthAttr, 10) : null;
|
||||
|
||||
if (height && !width) {
|
||||
// If we have a height but no width, the width should honor the workpad ratio.
|
||||
width = Math.round(workpad.width * (height / workpad.height));
|
||||
} else if (width && !height) {
|
||||
// If we have a width but no height, the height should honor the workpad ratio.
|
||||
height = Math.round(workpad.height * (width / workpad.width));
|
||||
}
|
||||
|
||||
const stage = {
|
||||
height: height || workpad.height,
|
||||
width: width || workpad.width,
|
||||
page: page !== null ? page : workpad.page,
|
||||
};
|
||||
|
||||
const settings = {
|
||||
autoplay: {
|
||||
isEnabled: !!autoplay,
|
||||
interval: interval || '5s',
|
||||
},
|
||||
toolbar: {
|
||||
isAutohide: !!toolbar,
|
||||
},
|
||||
};
|
||||
|
||||
area.classList.add('kbnCanvas');
|
||||
area.removeAttribute(SHAREABLE);
|
||||
|
||||
render(
|
||||
[
|
||||
<style key="style">{`html body .kbnCanvas { height: ${stage.height}px; width: ${stage.width}px; }`}</style>,
|
||||
<App key="app" workpad={workpad} {...{ stage, settings }} />,
|
||||
],
|
||||
area
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This function processes all elements that have a valid share data attribute and
|
||||
* attempts to place the designated workpad within them.
|
||||
*/
|
||||
export const share = () => {
|
||||
const shareAreas = document.querySelectorAll(`[${SHAREABLE}]`);
|
||||
const validAreas = Array.from(shareAreas).filter(
|
||||
area => area.getAttribute(SHAREABLE) === 'canvas'
|
||||
);
|
||||
|
||||
validAreas.forEach(updateArea);
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,101 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Page component 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"height": 720,
|
||||
"overflow": "hidden",
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="root"
|
||||
id="page-d8b39380-a8f5-4a52-afe7-c8f6a4eade7b"
|
||||
style={
|
||||
Object {
|
||||
"background": "#b83c6f",
|
||||
"height": 720,
|
||||
"width": 1280,
|
||||
}
|
||||
}
|
||||
>
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Page contextual: austin 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"height": 720,
|
||||
"overflow": "hidden",
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="root"
|
||||
id="page-d8b39380-a8f5-4a52-afe7-c8f6a4eade7b"
|
||||
style={
|
||||
Object {
|
||||
"background": "#b83c6f",
|
||||
"height": 720,
|
||||
"width": 1280,
|
||||
}
|
||||
}
|
||||
>
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
RenderedElement
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Page contextual: hello 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"height": 720,
|
||||
"overflow": "hidden",
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="root"
|
||||
id="page-7186b301-f8a7-4c65-8b89-38d68d31cfc4"
|
||||
style={
|
||||
Object {
|
||||
"background": "#777777",
|
||||
"height": 720,
|
||||
"width": 1080,
|
||||
}
|
||||
}
|
||||
>
|
||||
RenderedElement
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,48 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/RenderedElement component 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"height": 100,
|
||||
"overflow": "hidden",
|
||||
"position": "relative",
|
||||
"width": 100,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/RenderedElement contextual: austin 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#000",
|
||||
"height": 720,
|
||||
"overflow": "hidden",
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
RenderedElement
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/RenderedElement contextual: hello 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"height": 720,
|
||||
"overflow": "hidden",
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
RenderedElement
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
import { ExampleContext } from '../../test/context_example';
|
||||
|
||||
import { Canvas, CanvasComponent } from '../canvas';
|
||||
import { sharedWorkpads } from '../../test';
|
||||
import { initialCanvasShareableState } from '../../context/state';
|
||||
const { austin } = sharedWorkpads;
|
||||
|
||||
storiesOf('shareables/Canvas', module)
|
||||
.add('contextual: austin', () => (
|
||||
<ExampleContext source="austin">
|
||||
<Canvas />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('contextual: hello', () => (
|
||||
<ExampleContext source="hello">
|
||||
<Canvas />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component', () => (
|
||||
<ExampleContext source="austin">
|
||||
<CanvasComponent
|
||||
onSetPage={action('onSetPage')}
|
||||
onSetScrubberVisible={action('onSetScrubberVisible')}
|
||||
refs={initialCanvasShareableState.refs}
|
||||
settings={initialCanvasShareableState.settings}
|
||||
stage={{
|
||||
height: 338,
|
||||
page: 0,
|
||||
width: 600,
|
||||
}}
|
||||
workpad={austin}
|
||||
/>
|
||||
</ExampleContext>
|
||||
));
|
|
@ -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 { ExampleContext } from '../../test/context_example';
|
||||
|
||||
import { Page, PageComponent } from '../page';
|
||||
import { sharedWorkpads } from '../../test';
|
||||
const { austin } = sharedWorkpads;
|
||||
|
||||
storiesOf('shareables/Page', module)
|
||||
.add('contextual: austin', () => (
|
||||
<ExampleContext source="austin" style={{ height: 720 }}>
|
||||
<Page index={3} />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('contextual: hello', () => (
|
||||
<ExampleContext source="hello" style={{ height: 720 }}>
|
||||
<Page index={0} />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component', () => (
|
||||
<ExampleContext source="austin" style={{ height: 720 }}>
|
||||
<PageComponent height={720} width={1280} page={austin.pages[3]} />
|
||||
</ExampleContext>
|
||||
));
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { ExampleContext } from '../../test/context_example';
|
||||
|
||||
// @ts-ignore
|
||||
import { image } from '../../../canvas_plugin_src/renderers/image';
|
||||
import { sharedWorkpads } from '../../test';
|
||||
import { RenderedElement, RenderedElementComponent } from '../rendered_element';
|
||||
|
||||
const { austin, hello } = sharedWorkpads;
|
||||
|
||||
storiesOf('shareables/RenderedElement', module)
|
||||
.add('contextual: hello', () => (
|
||||
<ExampleContext style={{ height: 720 }}>
|
||||
<RenderedElement element={hello.pages[0].elements[0]} index={0} />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('contextual: austin', () => (
|
||||
<ExampleContext style={{ height: 720, background: '#000' }}>
|
||||
<RenderedElement element={austin.pages[0].elements[0]} index={0} />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component', () => (
|
||||
<ExampleContext style={{ height: 100, width: 100 }}>
|
||||
<RenderedElementComponent
|
||||
index={0}
|
||||
fn={image()}
|
||||
element={{
|
||||
id: '123',
|
||||
position: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
height: 100,
|
||||
width: 100,
|
||||
angle: 0,
|
||||
parent: null,
|
||||
},
|
||||
expressionRenderable: {
|
||||
state: 'ready',
|
||||
value: {
|
||||
type: 'render',
|
||||
as: 'image',
|
||||
value: {
|
||||
type: 'image',
|
||||
mode: 'contain',
|
||||
dataurl:
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NC42MiA1MS4wMyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fS5jbHMtMSwuY2xzLTJ7c3Ryb2tlOiNmMzY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9LmNscy0ye2ZpbGw6bm9uZTt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkZsYWcgSWNvbjwvdGl0bGU+PGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+PGcgaWQ9IkxheWVyXzEtMiIgZGF0YS1uYW1lPSJMYXllciAxIj48cG9seWdvbiBjbGFzcz0iY2xzLTEiIHBvaW50cz0iNDIuOTMgMjguMTUgMSAyOC4xNSAxIDEgNDIuOTMgMSAzNS40NyAxNC41OCA0Mi45MyAyOC4xNSIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjEiIHkxPSIxIiB4Mj0iMSIgeTI9IjUxLjAzIi8+PC9nPjwvZz48L3N2Zz4=',
|
||||
},
|
||||
css: '.canvasRenderEl{\n\n}',
|
||||
containerStyle: {
|
||||
type: 'containerStyle',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
error: null,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ExampleContext>
|
||||
));
|
11
x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/__snapshots__/app.test.tsx.snap
generated
Normal file
11
x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/__snapshots__/app.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<App /> App renders properly 1`] = `
|
||||
"<div class=\\"root\\" style=\\"height: 768px; width: 1080px;\\"><div class=\\"container\\" style=\\"height: 720px; width: 1080px;\\"><div class=\\"page\\" style=\\"height: 720px; width: 1080px;\\"><div id=\\"page-7186b301-f8a7-4c65-8b89-38d68d31cfc4\\" class=\\"root\\" style=\\"height: 720px; width: 1080px; background: rgb(119, 119, 119);\\"><div class=\\"canvasPositionable canvasInteractable\\" style=\\"width: 1082px; height: 205.37748344370857px; margin-left: -541px; margin-top: -102.68874172185429px; position: absolute;\\"><div class=\\"root\\"><div class=\\"container s2042575598\\" style=\\"overflow: hidden;\\"><style type=\\"text/css\\">.s2042575598 .canvasRenderEl h1 {
|
||||
font-size: 150px; text-align: center; color: #d3d3d3;
|
||||
}
|
||||
</style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div><div class=\\"root\\" style=\\"height: 48px;\\"><div class=\\"root\\"><div class=\\"slideContainer\\"><div class=\\"root\\" style=\\"height: 100px; width: 150px;\\"><div class=\\"preview\\" style=\\"height: 100px; width: 150px;\\"><div id=\\"page-7186b301-f8a7-4c65-8b89-38d68d31cfc4\\" class=\\"root\\" style=\\"height: 720px; width: 1080px; background: rgb(119, 119, 119);\\"><div class=\\"canvasPositionable canvasInteractable\\" style=\\"width: 1082px; height: 205.37748344370857px; margin-left: -541px; margin-top: -102.68874172185429px; position: absolute;\\"><div class=\\"root\\"><div class=\\"container s2042575598\\" style=\\"overflow: hidden;\\"><style type=\\"text/css\\">.s2042575598 .canvasRenderEl h1 {
|
||||
font-size: 150px; text-align: center; color: #d3d3d3;
|
||||
}
|
||||
</style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div></div></div><div class=\\"bar\\" style=\\"bottom: 0px;\\"><div class=\\"euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem title\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><a class=\\"euiLink euiLink--primary\\" href=\\"https://www.elastic.co\\" rel=\\"\\" title=\\"Powered by Elastic.co\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--large euiIcon-isLoading\\" focusable=\\"false\\"></svg></a></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\" style=\\"min-width: 0; cursor: default;\\"><div class=\\"euiText euiText--small\\"><div class=\\"euiTextColor euiTextColor--ghost\\"><div class=\\"eui-textTruncate\\">My Canvas Workpad</div></div></div></div></div></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\" style=\\"margin: 0px 12px;\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button disabled=\\"\\" class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" data-test-subj=\\"pageControlsPrevPage\\" aria-label=\\"Previous Page\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"></svg></button></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button class=\\"euiButtonEmpty euiButtonEmpty--ghost euiButtonEmpty--small\\" type=\\"button\\" data-test-subj=\\"pageControlsCurrentPage\\"><span class=\\"euiButtonEmpty__content\\"><span class=\\"euiButtonEmpty__text\\"><div class=\\"euiText euiText--small\\"><div class=\\"euiTextColor euiTextColor--ghost\\">Page 1</div></div></span></span></button></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button disabled=\\"\\" class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" data-test-subj=\\"pageControlsNextPage\\" aria-label=\\"Next Page\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"></svg></button></div></div><div class=\\"euiFlexGroup euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><div class=\\"euiPopover euiPopover--anchorUpRight euiPopover--withTitle\\" id=\\"settings\\"><div class=\\"euiPopover__anchor\\"><button class=\\"euiButtonIcon euiButtonIcon--ghost\\" type=\\"button\\" aria-label=\\"Settings\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon\\" focusable=\\"false\\" aria-hidden=\\"true\\"></svg></button></div></div></div></div></div></div></div></div></div></div>"
|
||||
`;
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
One test relies on react-dom at a version of 16.9... it can be enabled
|
||||
once renovate completes the upgrade. Relevant code has been commented out
|
||||
in the meantime.
|
||||
*/
|
||||
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
import React from 'react';
|
||||
// import { act } from 'react-dom/test-utils';
|
||||
import { App } from '../app';
|
||||
import { sharedWorkpads, WorkpadNames, tick } from '../../test';
|
||||
import {
|
||||
getScrubber as scrubber,
|
||||
getScrubberSlideContainer as scrubberContainer,
|
||||
getPageControlsCenter as center,
|
||||
getSettingsTrigger as trigger,
|
||||
getContextMenuItems as menuItems,
|
||||
// getAutoplayTextField as autoplayText,
|
||||
// getAutoplayCheckbox as autoplayCheck,
|
||||
// getAutoplaySubmit as autoplaySubmit,
|
||||
getToolbarCheckbox as toolbarCheck,
|
||||
getCanvas as canvas,
|
||||
getFooter as footer,
|
||||
getPageControlsPrevious as previous,
|
||||
getPageControlsNext as next,
|
||||
} from '../../test/selectors';
|
||||
|
||||
// Mock the renderers
|
||||
jest.mock('../../supported_renderers');
|
||||
|
||||
// Mock the EuiPortal - `insertAdjacentElement is not supported in
|
||||
// `jsdom` 12. We're just going to render a `div` with the children
|
||||
// so the `enzyme` tests will be accurate.
|
||||
jest.mock('@elastic/eui/lib/components/portal/portal', () => {
|
||||
// Local constants are not supported in Jest mocks-- they must be
|
||||
// imported within the mock.
|
||||
// eslint-disable-next-line no-shadow
|
||||
const React = require.requireActual('react');
|
||||
return {
|
||||
EuiPortal: (props: any) => <div>{props.children}</div>,
|
||||
};
|
||||
});
|
||||
|
||||
const getWrapper: (name?: WorkpadNames) => ReactWrapper = (name = 'hello') => {
|
||||
const workpad = sharedWorkpads[name];
|
||||
const { height, width } = workpad;
|
||||
const stage = {
|
||||
height,
|
||||
width,
|
||||
page: 0,
|
||||
};
|
||||
|
||||
return mount(<App {...{ stage, workpad }} />);
|
||||
};
|
||||
|
||||
describe('<App />', () => {
|
||||
test('App renders properly', () => {
|
||||
expect(getWrapper().html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('App can be navigated', () => {
|
||||
const wrapper = getWrapper('austin');
|
||||
next(wrapper).simulate('click');
|
||||
expect(center(wrapper).text()).toEqual('Page 2 of 28');
|
||||
previous(wrapper).simulate('click');
|
||||
});
|
||||
|
||||
test('scrubber opens and closes', () => {
|
||||
const wrapper = getWrapper('austin');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(false);
|
||||
center(wrapper).simulate('click');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(true);
|
||||
});
|
||||
|
||||
test('can open scrubber and set page', () => {
|
||||
const wrapper = getWrapper('austin');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(false);
|
||||
center(wrapper).simulate('click');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(true);
|
||||
|
||||
// Click a page preview
|
||||
scrubberContainer(wrapper)
|
||||
.childAt(3) // Get the fourth page preview
|
||||
.childAt(0) // Get the click-responding element
|
||||
.simulate('click');
|
||||
expect(center(wrapper).text()).toEqual('Page 4 of 28');
|
||||
|
||||
// Focus and key press a page preview
|
||||
scrubberContainer(wrapper)
|
||||
.childAt(5) // Get the sixth page preview
|
||||
.childAt(0) // Get the click-responding element
|
||||
.simulate('focus')
|
||||
.simulate('keyPress');
|
||||
expect(center(wrapper).text()).toEqual('Page 6 of 28');
|
||||
});
|
||||
|
||||
test('autohide footer functions on mouseEnter + Leave', async () => {
|
||||
const wrapper = getWrapper();
|
||||
trigger(wrapper).simulate('click');
|
||||
await tick(20);
|
||||
menuItems(wrapper)
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
await tick(20);
|
||||
wrapper.update();
|
||||
expect(footer(wrapper).prop('isHidden')).toEqual(false);
|
||||
expect(footer(wrapper).prop('isAutohide')).toEqual(false);
|
||||
toolbarCheck(wrapper).simulate('change');
|
||||
expect(footer(wrapper).prop('isAutohide')).toEqual(true);
|
||||
canvas(wrapper).simulate('mouseEnter');
|
||||
expect(footer(wrapper).prop('isHidden')).toEqual(false);
|
||||
canvas(wrapper).simulate('mouseLeave');
|
||||
expect(footer(wrapper).prop('isHidden')).toEqual(true);
|
||||
});
|
||||
|
||||
test('scrubber hides if open when autohide is activated', async () => {
|
||||
const wrapper = getWrapper('austin');
|
||||
center(wrapper).simulate('click');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(true);
|
||||
|
||||
// Open the menu and activate toolbar hiding.
|
||||
trigger(wrapper).simulate('click');
|
||||
await tick(20);
|
||||
menuItems(wrapper)
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
await tick(20);
|
||||
wrapper.update();
|
||||
toolbarCheck(wrapper).simulate('change');
|
||||
await tick(20);
|
||||
|
||||
// Simulate the mouse leaving the container
|
||||
canvas(wrapper).simulate('mouseLeave');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(false);
|
||||
});
|
||||
|
||||
/*
|
||||
test('autoplay starts when triggered', async () => {
|
||||
const wrapper = getWrapper('austin');
|
||||
trigger(wrapper).simulate('click');
|
||||
await tick(20);
|
||||
menuItems(wrapper)
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
await tick(20);
|
||||
wrapper.update();
|
||||
autoplayText(wrapper).simulate('change', { target: { value: '1s' } });
|
||||
autoplaySubmit(wrapper).simulate('submit');
|
||||
autoplayCheck(wrapper).simulate('change');
|
||||
expect(center(wrapper).text()).toEqual('Page 1 of 28');
|
||||
|
||||
await act(async () => {
|
||||
await tick(1500);
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
expect(center(wrapper).text()).not.toEqual('Page 1 of 28');
|
||||
});
|
||||
*/
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { mount, ReactWrapper } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../test/context_jest';
|
||||
import { getScrubber as scrubber, getPageControlsCenter as center } from '../../test/selectors';
|
||||
import { Canvas } from '../canvas';
|
||||
|
||||
jest.mock('../../supported_renderers');
|
||||
|
||||
describe('<Canvas />', () => {
|
||||
test('null workpad renders nothing', () => {
|
||||
expect(mount(<Canvas />).isEmptyRender());
|
||||
});
|
||||
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<JestContext source="austin">
|
||||
<Canvas />
|
||||
</JestContext>
|
||||
);
|
||||
});
|
||||
|
||||
test('scrubber opens and closes', () => {
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(false);
|
||||
center(wrapper).simulate('click');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { Page } from '../page';
|
||||
|
||||
describe('<Page />', () => {
|
||||
test('null workpad renders nothing', () => {
|
||||
expect(mount(<Page index={0} />).isEmptyRender());
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { CanvasRenderedWorkpad, CanvasShareableState, Stage } from '../types';
|
||||
import { RendererSpec } from '../../types';
|
||||
import { initialCanvasShareableState, CanvasShareableStateProvider } from '../context';
|
||||
import { Canvas } from './canvas';
|
||||
import { renderFunctions } from '../supported_renderers';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* An object describing the state of the workpad container.
|
||||
*/
|
||||
stage: Stage;
|
||||
|
||||
/**
|
||||
* The workpad being rendered within the shareable area.
|
||||
*/
|
||||
workpad: CanvasRenderedWorkpad;
|
||||
}
|
||||
|
||||
/**
|
||||
* The overall Canvas Shareable Workpad app; the highest-layer component.
|
||||
*/
|
||||
export const App: FC<Props> = ({ workpad, stage }) => {
|
||||
const renderers: { [key: string]: RendererSpec } = {};
|
||||
|
||||
renderFunctions.forEach(fn => {
|
||||
const func = fn();
|
||||
renderers[func.name] = func;
|
||||
});
|
||||
|
||||
const initialState: CanvasShareableState = {
|
||||
...initialCanvasShareableState,
|
||||
stage,
|
||||
renderers,
|
||||
workpad,
|
||||
};
|
||||
|
||||
return (
|
||||
<CanvasShareableStateProvider initialState={initialState}>
|
||||
<Canvas />
|
||||
</CanvasShareableStateProvider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
:global .kbnCanvas :local .root {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: height 1s;
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: canvas from global;
|
||||
composes: canvasContainer from global;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .page {
|
||||
position: absolute;
|
||||
transform-origin: center center;
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { useCanvasShareableState, setPageAction, setScrubberVisibleAction } from '../context';
|
||||
import { Page } from './page';
|
||||
import { Footer, FOOTER_HEIGHT } from './footer';
|
||||
import { getTimeInterval } from '../../public/lib/time_interval';
|
||||
|
||||
import css from './canvas.module.scss';
|
||||
import { CanvasRenderedWorkpad, Stage, Settings, Refs } from '../types';
|
||||
|
||||
let timeout: number = 0;
|
||||
|
||||
export type onSetPageFn = (page: number) => void;
|
||||
export type onSetScrubberVisibleFn = (visible: boolean) => void;
|
||||
type Workpad = Pick<CanvasRenderedWorkpad, 'height' | 'width' | 'pages'>;
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The handler to invoke when a page is selected.
|
||||
*/
|
||||
onSetPage: onSetPageFn;
|
||||
|
||||
/**
|
||||
* The handler to invoke when the Scrubber is shown or hidden.
|
||||
*/
|
||||
onSetScrubberVisible: onSetScrubberVisibleFn;
|
||||
|
||||
/**
|
||||
* The `react` `ref` objects pertaining to the stage.
|
||||
*/
|
||||
refs: Pick<Refs, 'stage'>;
|
||||
|
||||
/**
|
||||
* An object describing the current settings of the Shareable.
|
||||
*/
|
||||
settings: Settings;
|
||||
|
||||
/**
|
||||
* An object describing the state of the workpad container.
|
||||
*/
|
||||
stage: Stage;
|
||||
|
||||
/**
|
||||
* The workpad being rendered within the Shareable area.
|
||||
*/
|
||||
workpad: Workpad;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "canvas" for a workpad, which composes the toolbar and other components.
|
||||
*/
|
||||
export const CanvasComponent = ({
|
||||
onSetPage,
|
||||
onSetScrubberVisible,
|
||||
refs,
|
||||
settings,
|
||||
stage,
|
||||
workpad,
|
||||
}: Props) => {
|
||||
const { toolbar, autoplay } = settings;
|
||||
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: workpadHeight,
|
||||
transform,
|
||||
width: workpadWidth,
|
||||
};
|
||||
|
||||
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
|
||||
// killed on React re-render-- otherwise the pages will start bouncing around
|
||||
// as timeouts are accumulated.
|
||||
clearTimeout(timeout);
|
||||
|
||||
timeout = setTimeout(
|
||||
() => onSetPage(page >= workpad.pages.length - 1 ? 0 : page + 1),
|
||||
getTimeInterval(autoplay.interval)
|
||||
);
|
||||
}
|
||||
|
||||
const [toolbarHidden, setToolbarHidden] = useState(toolbar.isAutohide);
|
||||
const rootHeight = stageHeight + (toolbar.isAutohide ? 0 : FOOTER_HEIGHT);
|
||||
|
||||
const hideToolbar = (hidden: boolean) => {
|
||||
if (toolbar.isAutohide) {
|
||||
if (hidden) {
|
||||
// Hide the scrubber if we hide the toolbar.
|
||||
onSetScrubberVisible(false);
|
||||
}
|
||||
setToolbarHidden(hidden);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css.root}
|
||||
style={{ height: rootHeight, width: stageWidth }}
|
||||
onMouseEnter={() => hideToolbar(false)}
|
||||
onMouseLeave={() => hideToolbar(true)}
|
||||
ref={refs.stage}
|
||||
>
|
||||
<div className={css.container} style={{ height: stageHeight, width: stageWidth }}>
|
||||
<div className={css.page} style={pageStyle}>
|
||||
<Page index={page} />
|
||||
</div>
|
||||
</div>
|
||||
<Footer isHidden={toolbarHidden} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A store-connected container for the `Canvas` component.
|
||||
*/
|
||||
export const Canvas = () => {
|
||||
const [{ workpad, stage, settings, refs }, dispatch] = useCanvasShareableState();
|
||||
|
||||
if (!workpad) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onSetPage: onSetPageFn = (page: number) => {
|
||||
dispatch(setPageAction(page));
|
||||
};
|
||||
|
||||
const onSetScrubberVisible: onSetScrubberVisibleFn = (visible: boolean) => {
|
||||
dispatch(setScrubberVisibleAction(visible));
|
||||
};
|
||||
|
||||
return (
|
||||
<CanvasComponent
|
||||
{...{
|
||||
onSetPage,
|
||||
onSetScrubberVisible,
|
||||
refs,
|
||||
settings,
|
||||
stage,
|
||||
workpad,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,140 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Footer/components PageControls 1`] = `
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 12px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Previous Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsPrevPage"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--ghost euiButtonEmpty--small"
|
||||
data-test-subj="pageControlsCurrentPage"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
Page
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Next Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsNextPage"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/components Title 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
style={
|
||||
Object {
|
||||
"cursor": "default",
|
||||
"minWidth": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<div
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
This is a test title.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,302 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Footer/PageControls component 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 12px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Previous Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsPrevPage"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--ghost euiButtonEmpty--small"
|
||||
data-test-subj="pageControlsCurrentPage"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
Page
|
||||
1
|
||||
of 10
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Next Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsNextPage"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/PageControls contextual: austin 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"height": undefined,
|
||||
"overflow": "hidden",
|
||||
"padding": 10,
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 12px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Previous Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsPrevPage"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--ghost euiButtonEmpty--small"
|
||||
data-test-subj="pageControlsCurrentPage"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
Page
|
||||
1
|
||||
of 28
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Next Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsNextPage"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/PageControls contextual: hello 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"height": undefined,
|
||||
"overflow": "hidden",
|
||||
"padding": 10,
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
style={
|
||||
Object {
|
||||
"margin": "0 12px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Previous Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsPrevPage"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--ghost euiButtonEmpty--small"
|
||||
data-test-subj="pageControlsCurrentPage"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
Page
|
||||
1
|
||||
of 28
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-label="Next Page"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
data-test-subj="pageControlsNextPage"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,193 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Footer/Title component 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"height": undefined,
|
||||
"overflow": "hidden",
|
||||
"padding": 10,
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<a
|
||||
className="euiLink euiLink--primary"
|
||||
href="https://www.elastic.co"
|
||||
rel=""
|
||||
title="Powered by Elastic.co"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--large euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
style={
|
||||
Object {
|
||||
"cursor": "default",
|
||||
"minWidth": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<div
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
This is a test title.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/Title contextual: austin 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"height": undefined,
|
||||
"overflow": "hidden",
|
||||
"padding": 10,
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<a
|
||||
className="euiLink euiLink--primary"
|
||||
href="https://www.elastic.co"
|
||||
rel=""
|
||||
title="Powered by Elastic.co"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--large euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
style={
|
||||
Object {
|
||||
"cursor": "default",
|
||||
"minWidth": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<div
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
Elastic{ON} - Austin from Clint Andrew Hall with a title that just goes and goes and goes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/Title contextual: hello 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"height": undefined,
|
||||
"overflow": "hidden",
|
||||
"padding": 10,
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<a
|
||||
className="euiLink euiLink--primary"
|
||||
href="https://www.elastic.co"
|
||||
rel=""
|
||||
title="Powered by Elastic.co"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--large euiIcon-isLoading"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
style={
|
||||
Object {
|
||||
"cursor": "default",
|
||||
"minWidth": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<div
|
||||
className="euiTextColor euiTextColor--ghost"
|
||||
>
|
||||
<div
|
||||
className="eui-textTruncate"
|
||||
>
|
||||
My Canvas Workpad
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { ExampleContext } from '../../../test/context_example';
|
||||
|
||||
import { Footer } from '../footer';
|
||||
|
||||
storiesOf('shareables/Footer', module)
|
||||
.add('contextual: hello', () => (
|
||||
<ExampleContext height={172} source="hello">
|
||||
<Footer />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('contextual: austin', () => (
|
||||
<ExampleContext height={172} source="austin">
|
||||
<Footer />
|
||||
</ExampleContext>
|
||||
));
|
|
@ -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 { ExampleContext } from '../../../test/context_example';
|
||||
import { PageControls, PageControlsComponent } from '../page_controls';
|
||||
|
||||
const style = { background: '#333', padding: 10 };
|
||||
|
||||
storiesOf('shareables/Footer/PageControls', module)
|
||||
.add('contextual: hello', () => (
|
||||
<ExampleContext source="austin" {...{ style }}>
|
||||
<PageControls />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('contextual: austin', () => (
|
||||
<ExampleContext source="austin" {...{ style }}>
|
||||
<PageControls />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component', () => (
|
||||
<div {...{ style }}>
|
||||
<PageControlsComponent
|
||||
page={0}
|
||||
totalPages={10}
|
||||
onSetPageNumber={action('onSetPageNumber')}
|
||||
onToggleScrubber={action('onToggleScrubber')}
|
||||
/>
|
||||
</div>
|
||||
));
|
|
@ -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 { storiesOf } from '@storybook/react';
|
||||
|
||||
import { CanvasRenderedPage } from '../../../types';
|
||||
import { ExampleContext } from '../../../test/context_example';
|
||||
import { Scrubber, ScrubberComponent } from '../scrubber';
|
||||
import { workpads } from '../../../../__tests__/fixtures/workpads';
|
||||
|
||||
storiesOf('shareables/Footer/Scrubber', module)
|
||||
.add('contextual: hello', () => (
|
||||
<ExampleContext source="hello" style={{ height: 172 }} isScrubberVisible={true}>
|
||||
<Scrubber />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('contextual: austin', () => (
|
||||
<ExampleContext source="austin" style={{ height: 172 }} isScrubberVisible={true}>
|
||||
<Scrubber />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component', () => (
|
||||
<ExampleContext style={{ height: 172 }}>
|
||||
<ScrubberComponent
|
||||
isScrubberVisible={true}
|
||||
pages={(workpads[0].pages as unknown) as CanvasRenderedPage[]}
|
||||
/>
|
||||
</ExampleContext>
|
||||
));
|
|
@ -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 { storiesOf } from '@storybook/react';
|
||||
|
||||
import { ExampleContext } from '../../../test/context_example';
|
||||
import { Title, TitleComponent } from '../title';
|
||||
|
||||
const style = { background: '#333', padding: 10 };
|
||||
|
||||
storiesOf('shareables/Footer/Title', module)
|
||||
.add('contextual: hello', () => (
|
||||
<ExampleContext source="hello" {...{ style }}>
|
||||
<Title />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('contextual: austin', () => (
|
||||
<ExampleContext source="austin" {...{ style }}>
|
||||
<Title />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component', () => (
|
||||
<ExampleContext {...{ style }}>
|
||||
<TitleComponent title="This is a test title." />
|
||||
</ExampleContext>
|
||||
));
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../test/context_jest';
|
||||
import { getScrubber as scrubber, getPageControlsCenter as center } from '../../../test/selectors';
|
||||
import { Footer } from '../footer';
|
||||
|
||||
jest.mock('../../../supported_renderers');
|
||||
|
||||
describe('<Footer />', () => {
|
||||
test('null workpad renders nothing', () => {
|
||||
expect(mount(<Footer />).isEmptyRender());
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<JestContext>
|
||||
<Footer />
|
||||
</JestContext>
|
||||
);
|
||||
|
||||
test('scrubber functions properly', () => {
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(false);
|
||||
center(wrapper).simulate('click');
|
||||
expect(scrubber(wrapper).prop('isScrubberVisible')).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../test/context_jest';
|
||||
import {
|
||||
getPageControlsPrevious as previous,
|
||||
getPageControlsCenter as current,
|
||||
getPageControlsNext as next,
|
||||
} from '../../../test/selectors';
|
||||
import { PageControls } from '../page_controls';
|
||||
|
||||
jest.mock('../../../supported_renderers');
|
||||
|
||||
describe('<PageControls />', () => {
|
||||
test('null workpad renders nothing', () => {
|
||||
expect(mount(<PageControls />).isEmptyRender());
|
||||
});
|
||||
|
||||
const hello = mount(
|
||||
<JestContext source="hello">
|
||||
<PageControls />
|
||||
</JestContext>
|
||||
);
|
||||
const austin = mount(
|
||||
<JestContext source="austin">
|
||||
<PageControls />
|
||||
</JestContext>
|
||||
);
|
||||
|
||||
test('hello: renders as expected', () => {
|
||||
expect(previous(hello).props().disabled).toEqual(true);
|
||||
expect(next(hello).props().disabled).toEqual(true);
|
||||
expect(current(hello).text()).toEqual('Page 1');
|
||||
});
|
||||
|
||||
test('austin: renders as expected', () => {
|
||||
expect(previous(austin).props().disabled).toEqual(true);
|
||||
expect(next(austin).props().disabled).toEqual(false);
|
||||
expect(current(austin).text()).toEqual('Page 1 of 28');
|
||||
});
|
||||
|
||||
test('austin: moves between pages', () => {
|
||||
next(austin).simulate('click');
|
||||
expect(current(austin).text()).toEqual('Page 2 of 28');
|
||||
next(austin).simulate('click');
|
||||
expect(current(austin).text()).toEqual('Page 3 of 28');
|
||||
previous(austin).simulate('click');
|
||||
expect(current(austin).text()).toEqual('Page 2 of 28');
|
||||
});
|
||||
});
|
|
@ -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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../test/context_jest';
|
||||
import { PagePreview } from '../page_preview';
|
||||
import { getRenderedElement as element } from '../../../test/selectors';
|
||||
|
||||
jest.mock('../../../supported_renderers');
|
||||
|
||||
describe('<PagePreview />', () => {
|
||||
test('null workpad renders nothing', () => {
|
||||
expect(mount(<PagePreview height={100} index={0} />).isEmptyRender());
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<JestContext>
|
||||
<PagePreview height={100} index={0} />
|
||||
</JestContext>
|
||||
);
|
||||
|
||||
test('renders as expected', () => {
|
||||
expect(element(wrapper).text()).toEqual('markdown mock');
|
||||
});
|
||||
});
|
|
@ -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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../test/context_jest';
|
||||
import { Scrubber } from '../scrubber';
|
||||
import {
|
||||
getScrubberSlideContainer as container,
|
||||
getRenderedElement as element,
|
||||
} from '../../../test/selectors';
|
||||
|
||||
jest.mock('../../../supported_renderers');
|
||||
|
||||
describe('<Scrubber />', () => {
|
||||
test('null workpad renders nothing', () => {
|
||||
expect(mount(<Scrubber />).isEmptyRender());
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<JestContext>
|
||||
<Scrubber />
|
||||
</JestContext>
|
||||
);
|
||||
|
||||
test('renders as expected', () => {
|
||||
expect(container(wrapper).children().length === 1);
|
||||
expect(element(wrapper).text()).toEqual('markdown mock');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../test/context_jest';
|
||||
import { Title } from '../title';
|
||||
|
||||
jest.mock('../../../supported_renderers');
|
||||
|
||||
describe('<Title />', () => {
|
||||
test('null workpad renders nothing', () => {
|
||||
expect(mount(<Title />).isEmptyRender());
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<JestContext>
|
||||
<Title />
|
||||
</JestContext>
|
||||
);
|
||||
|
||||
test('renders as expected', () => {
|
||||
expect(wrapper.text()).toEqual('My Canvas Workpad');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
@import '@elastic/eui/src/global_styling/variables/_size.scss';
|
||||
@import '@elastic/eui/src/global_styling/variables/_colors.scss';
|
||||
|
||||
:global .kbnCanvas :local .root .bar {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bar {
|
||||
composes: euiBottomBar from global;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .bar {
|
||||
transition: bottom 0.25s;
|
||||
padding: $euiSizeM;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .title {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
:global .kbnCanvas .euiIcon__fillNegative {
|
||||
fill: $euiColorGhost !important;
|
||||
}
|
|
@ -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, { FC } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { useCanvasShareableState } from '../../context';
|
||||
import { Scrubber } from './scrubber';
|
||||
import { Title } from './title';
|
||||
import { PageControls } from './page_controls';
|
||||
import { Settings } from './settings';
|
||||
|
||||
import css from './footer.module.scss';
|
||||
|
||||
export const FOOTER_HEIGHT = 48;
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
* True if the footer should be hidden when not interacted with, false otherwise.
|
||||
*/
|
||||
isAutohide?: boolean;
|
||||
|
||||
/**
|
||||
* True if the footer should be hidden, false otherwise.
|
||||
*/
|
||||
isHidden?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Footer of the Shareable Canvas Workpad.
|
||||
*/
|
||||
export const FooterComponent: FC<Props> = ({ isAutohide = false, isHidden = false }) => {
|
||||
const { root, bar, title } = css;
|
||||
|
||||
return (
|
||||
<div className={root} style={{ height: FOOTER_HEIGHT }}>
|
||||
<Scrubber />
|
||||
<div className={bar} style={{ bottom: isAutohide && isHidden ? -FOOTER_HEIGHT : 0 }}>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem className={title}>
|
||||
<Title />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<PageControls />
|
||||
<Settings />
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A store-connected container for the `Footer` component.
|
||||
*/
|
||||
export const Footer: FC<Pick<Props, 'isHidden'>> = ({ isHidden = false }) => {
|
||||
const [{ workpad, settings }] = useCanvasShareableState();
|
||||
|
||||
if (!workpad) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { toolbar } = settings;
|
||||
const { isAutohide } = toolbar;
|
||||
|
||||
return <FooterComponent {...{ isHidden, isAutohide }} />;
|
||||
};
|
|
@ -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 './footer';
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiButtonEmpty, EuiText } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
useCanvasShareableState,
|
||||
setScrubberVisibleAction,
|
||||
setPageAction,
|
||||
setAutoplayAction,
|
||||
} from '../../context';
|
||||
|
||||
type onSetPageNumberFn = (page: number) => void;
|
||||
type onToggleScrubberFn = () => void;
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The handler to invoke when the current page number is set.
|
||||
*/
|
||||
onSetPageNumber: onSetPageNumberFn;
|
||||
|
||||
/**
|
||||
* The handler to invoke when the scrubber visibility is toggled.
|
||||
*/
|
||||
onToggleScrubber: onToggleScrubberFn;
|
||||
|
||||
/**
|
||||
* The current page number.
|
||||
*/
|
||||
page: number;
|
||||
|
||||
/**
|
||||
* The total number of pages in the worpad.
|
||||
*/
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The page count and paging controls within the footer of the Shareable Canvas Workpad.
|
||||
*/
|
||||
export const PageControlsComponent: FC<Props> = ({
|
||||
onSetPageNumber,
|
||||
page,
|
||||
totalPages,
|
||||
onToggleScrubber,
|
||||
}) => {
|
||||
const currentPage = page + 1;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" style={{ margin: '0 12px' }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
color="ghost"
|
||||
data-test-subj="pageControlsPrevPage"
|
||||
onClick={() => onSetPageNumber(page - 1)}
|
||||
iconType="arrowLeft"
|
||||
disabled={currentPage <= 1}
|
||||
aria-label="Previous Page"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
color="ghost"
|
||||
size="s"
|
||||
onClick={onToggleScrubber}
|
||||
data-test-subj="pageControlsCurrentPage"
|
||||
>
|
||||
<EuiText color="ghost" size="s">
|
||||
Page {currentPage}
|
||||
{totalPages > 1 ? ` of ${totalPages}` : null}
|
||||
</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
color="ghost"
|
||||
data-test-subj="pageControlsNextPage"
|
||||
onClick={() => onSetPageNumber(page + 1)}
|
||||
iconType="arrowRight"
|
||||
disabled={currentPage >= totalPages}
|
||||
aria-label="Next Page"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A store-connected container for the `PageControls` component.
|
||||
*/
|
||||
export const PageControls: FC<{}> = () => {
|
||||
const [{ workpad, footer, stage }, dispatch] = useCanvasShareableState();
|
||||
|
||||
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 }} />;
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
@import '@elastic/eui/src/global_styling/variables/_size.scss';
|
||||
|
||||
:global .kbnCanvas :local .root {
|
||||
margin: 0 $euiSizeS;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .root :global .canvasPage {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .preview {
|
||||
pointer-events: none;
|
||||
transform-origin: top left;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { PageComponent } from '../page';
|
||||
import { CanvasRenderedPage } from '../../types';
|
||||
import { useCanvasShareableState } from '../../context';
|
||||
import { setPageAction } from '../../context/actions';
|
||||
|
||||
import css from './page_preview.module.scss';
|
||||
|
||||
type onClickFn = (index: number) => void;
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
* The height of the preview container.
|
||||
*/
|
||||
height: number;
|
||||
|
||||
/**
|
||||
* The index of the preview relative to other pages in the workpad.
|
||||
*/
|
||||
index: number;
|
||||
|
||||
/**
|
||||
* The handler to invoke if the preview is clicked.
|
||||
*/
|
||||
onClick: onClickFn;
|
||||
|
||||
/**
|
||||
* An object describing the page.
|
||||
*/
|
||||
page: CanvasRenderedPage;
|
||||
|
||||
/**
|
||||
* The height of the workpad.
|
||||
*/
|
||||
workpadHeight: number;
|
||||
|
||||
/**
|
||||
* The width of the workpad.
|
||||
*/
|
||||
workpadWidth: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The small preview of the page shown within the `Scrubber`.
|
||||
*/
|
||||
export const PagePreviewComponent: FC<Props> = ({
|
||||
height,
|
||||
index,
|
||||
onClick,
|
||||
page,
|
||||
workpadHeight,
|
||||
workpadWidth,
|
||||
}) => {
|
||||
const scale = height / workpadHeight;
|
||||
const style = {
|
||||
height: workpadHeight * scale,
|
||||
width: workpadWidth * scale,
|
||||
};
|
||||
|
||||
const transform = {
|
||||
...style,
|
||||
transform: `scale3d(${scale}, ${scale}, 1)`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css.root}
|
||||
onClick={() => onClick(index)}
|
||||
onKeyPress={() => onClick(index)}
|
||||
style={style}
|
||||
>
|
||||
<div className={css.preview} style={transform}>
|
||||
<PageComponent {...{ page }} height={workpadHeight} width={workpadWidth} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A store-connected container for the `PagePreview` component.
|
||||
*/
|
||||
export const PagePreview: FC<Pick<Props, 'index' | 'height'>> = ({ index, height }) => {
|
||||
const [{ workpad }, dispatch] = useCanvasShareableState();
|
||||
|
||||
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 }} />
|
||||
);
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
@import '@elastic/eui/src/global_styling/variables/_size.scss';
|
||||
@import '@elastic/eui/src/global_styling/variables/_colors.scss';
|
||||
@import '@elastic/eui/src/global_styling/mixins/_helpers.scss';
|
||||
|
||||
:global .kbnCanvas :local .root {
|
||||
background: $euiColorGhost;
|
||||
position: absolute;
|
||||
bottom: -172px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: $euiSizeS 0 ($euiSizeL * 2) 0;
|
||||
transition: bottom 0.25s;
|
||||
height: 172px;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .visible {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:global .kbnCanvas :local .slideContainer {
|
||||
@include euiScrollBar;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { PagePreview } from './page_preview';
|
||||
import { useCanvasShareableState } from '../../context';
|
||||
|
||||
import css from './scrubber.module.scss';
|
||||
import { CanvasRenderedPage } from '../../types';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* True if the scrubber is currently visible, false otherwise.
|
||||
*/
|
||||
isScrubberVisible: boolean;
|
||||
|
||||
/**
|
||||
* A collection of objects describing the pages within the workpad to be
|
||||
* displayed in the Scrubber.
|
||||
*/
|
||||
pages: CanvasRenderedPage[];
|
||||
}
|
||||
|
||||
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 ScrubberComponent: FC<Props> = ({ isScrubberVisible, pages }) => {
|
||||
const className = isScrubberVisible ? classnames(css.root, css.visible) : css.root;
|
||||
|
||||
const slides = pages.map((page, index) => (
|
||||
<PagePreview key={page.id} height={THUMBNAIL_HEIGHT} {...{ index }} />
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={css.slideContainer}>{slides}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A store-connected container for the `Scrubber` component.
|
||||
*/
|
||||
export const Scrubber: FC<{}> = () => {
|
||||
const [{ workpad, footer }] = useCanvasShareableState();
|
||||
|
||||
if (!workpad) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { pages } = workpad;
|
||||
const { isScrubberVisible } = footer;
|
||||
|
||||
return <ScrubberComponent {...{ pages, isScrubberVisible }} />;
|
||||
};
|
|
@ -0,0 +1,532 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings/AutoplaySettings component: off, 2s 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#fff",
|
||||
"border": "1px solid #ccc",
|
||||
"height": 228,
|
||||
"overflow": "hidden",
|
||||
"padding": 16,
|
||||
"position": "relative",
|
||||
"width": 256,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="euiSwitch__input"
|
||||
id="cycle"
|
||||
name="cycle"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="cycle"
|
||||
>
|
||||
Cycle Slides
|
||||
</label>
|
||||
</div>
|
||||
<hr
|
||||
className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium"
|
||||
/>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow euiFormRow--compressed"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Set a custom interval
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--compressed"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
className="euiFieldText euiFieldText--compressed"
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="text"
|
||||
value="2s"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Use shorthand notation, like 30s, 10m, or 1h
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--small"
|
||||
disabled={false}
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "auto",
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="euiButton__content"
|
||||
>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
Set
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings/AutoplaySettings component: on, 5s 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#fff",
|
||||
"border": "1px solid #ccc",
|
||||
"height": 228,
|
||||
"overflow": "hidden",
|
||||
"padding": 16,
|
||||
"position": "relative",
|
||||
"width": 256,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
className="euiSwitch__input"
|
||||
id="cycle"
|
||||
name="cycle"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="cycle"
|
||||
>
|
||||
Cycle Slides
|
||||
</label>
|
||||
</div>
|
||||
<hr
|
||||
className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium"
|
||||
/>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow euiFormRow--compressed"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Set a custom interval
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--compressed"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
className="euiFieldText euiFieldText--compressed"
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="text"
|
||||
value="5s"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Use shorthand notation, like 30s, 10m, or 1h
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--small"
|
||||
disabled={false}
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "auto",
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="euiButton__content"
|
||||
>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
Set
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings/AutoplaySettings contextual 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#fff",
|
||||
"border": "1px solid #ccc",
|
||||
"height": 228,
|
||||
"overflow": "hidden",
|
||||
"padding": 16,
|
||||
"position": "relative",
|
||||
"width": 256,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="euiSwitch__input"
|
||||
id="cycle"
|
||||
name="cycle"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="cycle"
|
||||
>
|
||||
Cycle Slides
|
||||
</label>
|
||||
</div>
|
||||
<hr
|
||||
className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium"
|
||||
/>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow euiFormRow--compressed"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Set a custom interval
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--compressed"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
className="euiFieldText euiFieldText--compressed"
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="text"
|
||||
value="5s"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Use shorthand notation, like 30s, 10m, or 1h
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--small"
|
||||
disabled={false}
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "auto",
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="euiButton__content"
|
||||
>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
Set
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,479 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Settings/components AutoplaySettings, autoplay disabled 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
className="euiSwitch__input"
|
||||
id="cycle"
|
||||
name="cycle"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="cycle"
|
||||
>
|
||||
Cycle Slides
|
||||
</label>
|
||||
</div>
|
||||
<hr
|
||||
className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium"
|
||||
/>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow euiFormRow--compressed"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Set a custom interval
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--compressed"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
className="euiFieldText euiFieldText--compressed"
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="text"
|
||||
value="5s"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Use shorthand notation, like 30s, 10m, or 1h
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--small"
|
||||
disabled={false}
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "auto",
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="euiButton__content"
|
||||
>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
Set
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Settings/components AutoplaySettings, autoplay enabled 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
className="euiSwitch__input"
|
||||
id="cycle"
|
||||
name="cycle"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="cycle"
|
||||
>
|
||||
Cycle Slides
|
||||
</label>
|
||||
</div>
|
||||
<hr
|
||||
className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginMedium"
|
||||
/>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow euiFormRow--compressed"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Set a custom interval
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--compressed"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
className="euiFieldText euiFieldText--compressed"
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="text"
|
||||
value="5s"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Use shorthand notation, like 30s, 10m, or 1h
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--small"
|
||||
disabled={false}
|
||||
id="generated-id"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "auto",
|
||||
}
|
||||
}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="euiButton__content"
|
||||
>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
Set
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Settings/components ToolbarSettings, autohide disabled 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
checked={false}
|
||||
className="euiSwitch__input"
|
||||
data-test-subj="hideToolbarSwitch"
|
||||
id="generated-id"
|
||||
name="toolbarHide"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Hide Toolbar
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Hide the toolbar when the mouse is not within the Canvas?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Settings/components ToolbarSettings, autohide enabled 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
checked={true}
|
||||
className="euiSwitch__input"
|
||||
data-test-subj="hideToolbarSwitch"
|
||||
id="generated-id"
|
||||
name="toolbarHide"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Hide Toolbar
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Hide the toolbar when the mouse is not within the Canvas?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,113 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings component 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"height": undefined,
|
||||
"overflow": "hidden",
|
||||
"padding": 10,
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorUpRight euiPopover--withTitle"
|
||||
id="settings"
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Settings"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings contextual 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#333",
|
||||
"height": undefined,
|
||||
"overflow": "hidden",
|
||||
"padding": 10,
|
||||
"position": "relative",
|
||||
"width": undefined,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorUpRight euiPopover--withTitle"
|
||||
id="settings"
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Settings"
|
||||
className="euiButtonIcon euiButtonIcon--ghost"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiButtonIcon__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,277 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings/ToolbarSettings component: off 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#fff",
|
||||
"border": "1px solid #ccc",
|
||||
"height": 124,
|
||||
"overflow": "hidden",
|
||||
"padding": 16,
|
||||
"position": "relative",
|
||||
"width": 256,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
checked={false}
|
||||
className="euiSwitch__input"
|
||||
data-test-subj="hideToolbarSwitch"
|
||||
id="generated-id"
|
||||
name="toolbarHide"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Hide Toolbar
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Hide the toolbar when the mouse is not within the Canvas?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings/ToolbarSettings component: on 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#fff",
|
||||
"border": "1px solid #ccc",
|
||||
"height": 124,
|
||||
"overflow": "hidden",
|
||||
"padding": 16,
|
||||
"position": "relative",
|
||||
"width": 256,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
checked={true}
|
||||
className="euiSwitch__input"
|
||||
data-test-subj="hideToolbarSwitch"
|
||||
id="generated-id"
|
||||
name="toolbarHide"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Hide Toolbar
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Hide the toolbar when the mouse is not within the Canvas?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots shareables/Footer/Settings/ToolbarSettings contextual 1`] = `
|
||||
<div
|
||||
className="kbnCanvas"
|
||||
style={
|
||||
Object {
|
||||
"background": "#fff",
|
||||
"border": "1px solid #ccc",
|
||||
"height": 124,
|
||||
"overflow": "hidden",
|
||||
"padding": 16,
|
||||
"position": "relative",
|
||||
"width": 256,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFormRow"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiSwitch"
|
||||
>
|
||||
<input
|
||||
aria-describedby="generated-id-help"
|
||||
checked={false}
|
||||
className="euiSwitch__input"
|
||||
data-test-subj="hideToolbarSwitch"
|
||||
id="generated-id"
|
||||
name="toolbarHide"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__body"
|
||||
>
|
||||
<span
|
||||
className="euiSwitch__thumb"
|
||||
/>
|
||||
<span
|
||||
className="euiSwitch__track"
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<svg
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoading euiSwitch__icon euiSwitch__icon--checked"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<label
|
||||
className="euiSwitch__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Hide Toolbar
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormHelpText euiFormRow__text"
|
||||
id="generated-id-help"
|
||||
>
|
||||
Hide the toolbar when the mouse is not within the Canvas?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
import { ExampleContext } from '../../../../test/context_example';
|
||||
|
||||
import { AutoplaySettings, AutoplaySettingsComponent } from '../autoplay_settings';
|
||||
|
||||
const style = {
|
||||
width: 256,
|
||||
height: 228,
|
||||
padding: 16,
|
||||
border: '1px solid #ccc',
|
||||
background: '#fff',
|
||||
};
|
||||
|
||||
storiesOf('shareables/Footer/Settings/AutoplaySettings', module)
|
||||
.add('contextual', () => (
|
||||
<ExampleContext {...{ style }}>
|
||||
<AutoplaySettings />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component: off, 2s', () => (
|
||||
<ExampleContext {...{ style }}>
|
||||
<AutoplaySettingsComponent
|
||||
isEnabled={false}
|
||||
interval="2s"
|
||||
onSetAutoplay={action('onSetAutoplay')}
|
||||
onSetInterval={action('onSetInterval')}
|
||||
/>
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component: on, 5s', () => (
|
||||
<ExampleContext {...{ style }}>
|
||||
<AutoplaySettingsComponent
|
||||
isEnabled={true}
|
||||
interval="5s"
|
||||
onSetAutoplay={action('onSetAutoplay')}
|
||||
onSetInterval={action('onSetInterval')}
|
||||
/>
|
||||
</ExampleContext>
|
||||
));
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { ExampleContext } from '../../../../test/context_example';
|
||||
|
||||
import { Settings, SettingsComponent } from '../settings';
|
||||
import { initialCanvasShareableState } from '../../../../context';
|
||||
|
||||
storiesOf('shareables/Footer/Settings', module)
|
||||
.add('contextual', () => (
|
||||
<ExampleContext style={{ background: '#333', padding: 10 }}>
|
||||
<Settings />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component', () => (
|
||||
<ExampleContext style={{ background: '#333', padding: 10 }}>
|
||||
<SettingsComponent refs={initialCanvasShareableState.refs} />
|
||||
</ExampleContext>
|
||||
));
|
|
@ -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 { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
import { ExampleContext } from '../../../../test/context_example';
|
||||
|
||||
import { ToolbarSettings, ToolbarSettingsComponent } from '../toolbar_settings';
|
||||
|
||||
const style = {
|
||||
width: 256,
|
||||
height: 124,
|
||||
padding: 16,
|
||||
border: '1px solid #ccc',
|
||||
background: '#fff',
|
||||
};
|
||||
|
||||
storiesOf('shareables/Footer/Settings/ToolbarSettings', module)
|
||||
.add('contextual', () => (
|
||||
<ExampleContext {...{ style }}>
|
||||
<ToolbarSettings onSetAutohide={action('onSetAutohide')} />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component: on', () => (
|
||||
<ExampleContext {...{ style }}>
|
||||
<ToolbarSettingsComponent isAutohide={true} onSetAutohide={action('onSetAutohide')} />
|
||||
</ExampleContext>
|
||||
))
|
||||
.add('component: off', () => (
|
||||
<ExampleContext {...{ style }}>
|
||||
<ToolbarSettingsComponent isAutohide={false} onSetAutohide={action('onSetAutohide')} />
|
||||
</ExampleContext>
|
||||
));
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../../test/context_jest';
|
||||
import {
|
||||
getAutoplayTextField as input,
|
||||
getAutoplayCheckbox as checkbox,
|
||||
getAutoplaySubmit as submit,
|
||||
} from '../../../../test/selectors';
|
||||
import { AutoplaySettings } from '../autoplay_settings';
|
||||
|
||||
jest.mock('../../../../supported_renderers');
|
||||
|
||||
describe('<AutoplaySettings />', () => {
|
||||
const wrapper = mount(
|
||||
<JestContext>
|
||||
<AutoplaySettings />
|
||||
</JestContext>
|
||||
);
|
||||
|
||||
test('renders as expected', () => {
|
||||
expect(checkbox(wrapper).props().checked).toEqual(false);
|
||||
expect(input(wrapper).props().value).toBe('5s');
|
||||
});
|
||||
|
||||
test('activates and deactivates', () => {
|
||||
checkbox(wrapper).simulate('change');
|
||||
expect(checkbox(wrapper).props().checked).toEqual(true);
|
||||
checkbox(wrapper).simulate('change');
|
||||
expect(checkbox(wrapper).props().checked).toEqual(false);
|
||||
});
|
||||
|
||||
test('changes properly with input', () => {
|
||||
input(wrapper).simulate('change', { target: { value: '2asd' } });
|
||||
expect(submit(wrapper).props().disabled).toEqual(true);
|
||||
input(wrapper).simulate('change', { target: { value: '2s' } });
|
||||
expect(submit(wrapper).props().disabled).toEqual(false);
|
||||
expect(input(wrapper).props().value === '2s');
|
||||
submit(wrapper).simulate('submit');
|
||||
expect(input(wrapper).props().value === '2s');
|
||||
expect(submit(wrapper).props().disabled).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 { mount, ReactWrapper } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../../test/context_jest';
|
||||
import { takeMountedSnapshot, tick } from '../../../../test';
|
||||
import {
|
||||
getSettingsTrigger as trigger,
|
||||
getPopover as popover,
|
||||
getPortal as portal,
|
||||
getContextMenuItems as menuItems,
|
||||
} from '../../../../test/selectors';
|
||||
import { Settings } from '../settings';
|
||||
|
||||
jest.mock('../../../../supported_renderers');
|
||||
jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`);
|
||||
jest.mock('@elastic/eui/lib/components/portal/portal', () => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
const React = require.requireActual('react');
|
||||
return {
|
||||
EuiPortal: (props: any) => <div>{props.children}</div>,
|
||||
};
|
||||
});
|
||||
|
||||
describe('<Settings />', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
wrapper = mount(
|
||||
<JestContext stageRef={ref}>
|
||||
<div ref={ref}>
|
||||
<Settings />
|
||||
</div>
|
||||
</JestContext>
|
||||
);
|
||||
});
|
||||
|
||||
test('renders as expected', () => {
|
||||
expect(trigger(wrapper).exists()).toEqual(true);
|
||||
expect(portal(wrapper).exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('clicking settings opens and closes the menu', () => {
|
||||
trigger(wrapper).simulate('click');
|
||||
expect(portal(wrapper).exists()).toEqual(true);
|
||||
expect(popover(wrapper).prop('isOpen')).toEqual(true);
|
||||
expect(menuItems(wrapper).length).toEqual(2);
|
||||
expect(portal(wrapper).text()).toEqual('SettingsAuto PlayToolbar');
|
||||
trigger(wrapper).simulate('click');
|
||||
expect(popover(wrapper).prop('isOpen')).toEqual(false);
|
||||
});
|
||||
|
||||
test('can navigate Autoplay Settings', async () => {
|
||||
trigger(wrapper).simulate('click');
|
||||
expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot();
|
||||
await tick(20);
|
||||
menuItems(wrapper)
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
await tick(20);
|
||||
expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('can navigate Toolbar Settings, closes when activated', async () => {
|
||||
trigger(wrapper).simulate('click');
|
||||
expect(takeMountedSnapshot(portal(wrapper))).toMatchSnapshot();
|
||||
menuItems(wrapper)
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
// Wait for the animation and DOM update
|
||||
await tick(20);
|
||||
portal(wrapper).update();
|
||||
expect(portal(wrapper).html()).toMatchSnapshot();
|
||||
|
||||
// Click the Hide Toolbar switch
|
||||
portal(wrapper)
|
||||
.find('input[data-test-subj="hideToolbarSwitch"]')
|
||||
.simulate('change');
|
||||
|
||||
// Wait for the animation and DOM update
|
||||
await tick(20);
|
||||
portal(wrapper).update();
|
||||
|
||||
// The Portal should not be open.
|
||||
expect(popover(wrapper).prop('isOpen')).toEqual(false);
|
||||
expect(portal(wrapper).html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { JestContext } from '../../../../test/context_jest';
|
||||
import { getToolbarCheckbox as checkbox } from '../../../../test/selectors';
|
||||
import { ToolbarSettings } from '../toolbar_settings';
|
||||
|
||||
jest.mock('../../../../supported_renderers');
|
||||
|
||||
describe('<ToolbarSettings />', () => {
|
||||
const wrapper = mount(
|
||||
<JestContext>
|
||||
<ToolbarSettings onSetAutohide={() => {}} />
|
||||
</JestContext>
|
||||
);
|
||||
|
||||
test('renders as expected', () => {
|
||||
expect(checkbox(wrapper).props().checked).toEqual(false);
|
||||
});
|
||||
|
||||
test('activates and deactivates', () => {
|
||||
checkbox(wrapper).simulate('change');
|
||||
expect(checkbox(wrapper).props().checked).toEqual(true);
|
||||
checkbox(wrapper).simulate('change');
|
||||
expect(checkbox(wrapper).props().checked).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { EuiHorizontalRule, EuiSwitch } from '@elastic/eui';
|
||||
import {
|
||||
useCanvasShareableState,
|
||||
setAutoplayAction,
|
||||
setAutoplayIntervalAction,
|
||||
} 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 onSetAutoplayFn = (autoplay: boolean) => void;
|
||||
export type onSetIntervalFn = (interval: string) => void;
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
* True if autoplay is currently enabled, false otherwise.
|
||||
*/
|
||||
isEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The interval with which to move between pages.
|
||||
*/
|
||||
interval: string;
|
||||
|
||||
/**
|
||||
* The handler to invoke when Autoplay is enabled or disabled.
|
||||
*/
|
||||
onSetAutoplay: onSetAutoplayFn;
|
||||
|
||||
/**
|
||||
* The handler to invoke when the autoplay interval is set.
|
||||
*/
|
||||
onSetInterval: onSetIntervalFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* The panel used to configure Autolay in Shareable Canvas Workpads.
|
||||
*/
|
||||
export const AutoplaySettingsComponent: FC<Props> = ({
|
||||
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>
|
||||
);
|
||||
|
||||
/**
|
||||
* A store-connected container for the `AutoplaySettings` component.
|
||||
*/
|
||||
export const AutoplaySettings = () => {
|
||||
const [{ settings }, dispatch] = useCanvasShareableState();
|
||||
|
||||
const { autoplay } = settings;
|
||||
const { isEnabled, interval } = autoplay;
|
||||
|
||||
const onSetInterval: onSetIntervalFn = (newInterval: string) =>
|
||||
dispatch(setAutoplayIntervalAction(newInterval));
|
||||
|
||||
const onSetAutoplay: onSetAutoplayFn = (enabled: boolean) => dispatch(setAutoplayAction(enabled));
|
||||
|
||||
return <AutoplaySettingsComponent {...{ isEnabled, interval, onSetAutoplay, onSetInterval }} />;
|
||||
};
|
|
@ -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 './settings';
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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, { useState, FC } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiPopover, EuiContextMenu } from '@elastic/eui';
|
||||
import { useCanvasShareableState } from '../../../context';
|
||||
import { Refs } from '../../../types';
|
||||
import { ToolbarSettings } from './toolbar_settings';
|
||||
import { AutoplaySettings } from './autoplay_settings';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* A collection of React `ref` objects for the Shareable Runtime.
|
||||
*/
|
||||
refs: Refs;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Settings Popover for Canvas Shareable Workpads.
|
||||
*/
|
||||
export const SettingsComponent: FC<Props> = ({ refs }) => {
|
||||
const [isPopoverOpen, setPopoverOpen] = useState(false);
|
||||
const button = (
|
||||
<EuiButtonIcon
|
||||
color="ghost"
|
||||
iconType="gear"
|
||||
aria-label="Settings"
|
||||
onClick={() => setPopoverOpen(!isPopoverOpen)}
|
||||
/>
|
||||
);
|
||||
|
||||
const flattenPanelTree = (tree: any, array: any[] = []) => {
|
||||
array.push(tree);
|
||||
|
||||
if (tree.items) {
|
||||
tree.items.forEach((item: any) => {
|
||||
if (item.panel) {
|
||||
flattenPanelTree(item.panel, array);
|
||||
item.panel = item.panel.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
const panels = flattenPanelTree({
|
||||
id: 0,
|
||||
title: 'Settings',
|
||||
items: [
|
||||
{
|
||||
name: 'Auto Play',
|
||||
icon: 'play',
|
||||
panel: {
|
||||
id: 1,
|
||||
title: 'Auto Play',
|
||||
content: <AutoplaySettings />,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Toolbar',
|
||||
icon: 'boxesHorizontal',
|
||||
panel: {
|
||||
id: 2,
|
||||
title: 'Toolbar',
|
||||
content: <ToolbarSettings onSetAutohide={() => setPopoverOpen(false)} />,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="flexEnd" justifyContent="center" direction="column" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
closePopover={() => setPopoverOpen(false)}
|
||||
id="settings"
|
||||
isOpen={isPopoverOpen}
|
||||
button={button}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="upRight"
|
||||
insert={
|
||||
refs.stage.current ? { sibling: refs.stage.current, position: 'after' } : undefined
|
||||
}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A store-connected container for the `Settings` component.
|
||||
*/
|
||||
export const Settings: FC<{}> = () => {
|
||||
const [{ refs }] = useCanvasShareableState();
|
||||
|
||||
return <SettingsComponent refs={refs} />;
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { EuiSwitch, EuiFormRow } from '@elastic/eui';
|
||||
import { useCanvasShareableState, setToolbarAutohideAction } from '../../../context';
|
||||
|
||||
export type onSetAutohideFn = (isAutohide: boolean) => void;
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
* True if the toolbar should be hidden when the mouse is not within the workpad,
|
||||
* false otherwise.
|
||||
*/
|
||||
isAutohide: boolean;
|
||||
|
||||
/**
|
||||
* The handler to invoke when autohide is set.
|
||||
*/
|
||||
onSetAutohide: onSetAutohideFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* The settings panel for the Toolbar of a Shareable Canvas Workpad.
|
||||
*/
|
||||
export const ToolbarSettingsComponent = ({ isAutohide, onSetAutohide }: Props) => {
|
||||
return (
|
||||
<div style={{ padding: 16 }}>
|
||||
<EuiFormRow helpText="Hide the toolbar when the mouse is not within the Canvas?">
|
||||
<EuiSwitch
|
||||
data-test-subj="hideToolbarSwitch"
|
||||
name="toolbarHide"
|
||||
id="toolbarHide"
|
||||
label="Hide Toolbar"
|
||||
checked={isAutohide}
|
||||
onChange={() => onSetAutohide(!isAutohide)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A store-connected container for the `ToolbarSettings` component.
|
||||
*/
|
||||
export const ToolbarSettings: FC<Pick<Props, 'onSetAutohide'>> = ({ onSetAutohide }) => {
|
||||
const [{ settings }, dispatch] = useCanvasShareableState();
|
||||
|
||||
const { toolbar } = settings;
|
||||
const { isAutohide } = toolbar;
|
||||
|
||||
const onSetAutohideFn: onSetAutohideFn = (autohide: boolean) => {
|
||||
onSetAutohide(autohide);
|
||||
dispatch(setToolbarAutohideAction(autohide));
|
||||
};
|
||||
|
||||
return <ToolbarSettingsComponent onSetAutohide={onSetAutohideFn} {...{ isAutohide }} />;
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiLink } from '@elastic/eui';
|
||||
import { useCanvasShareableState } from '../../context';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The title of the workpad being shared.
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The title of the workpad displayed in the left-hand of the footer.
|
||||
*/
|
||||
export const TitleComponent: FC<Props> = ({ title }) => (
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexStart" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href="https://www.elastic.co" title="Powered by Elastic.co">
|
||||
<EuiIcon type="logoElastic" size="l" />
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 0, cursor: 'default' }}>
|
||||
<EuiText color="ghost" size="s">
|
||||
<div className="eui-textTruncate">{title}</div>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
/**
|
||||
* A store-connected container for the `Title` component.
|
||||
*/
|
||||
export const Title: FC<{}> = () => {
|
||||
const [{ workpad }] = useCanvasShareableState();
|
||||
|
||||
if (!workpad) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name: title } = workpad;
|
||||
|
||||
return <TitleComponent {...{ title }} />;
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
.root {
|
||||
composes: canvasPage from global;
|
||||
composes: canvasInteractivePage from global;
|
||||
composes: kbn-resetFocusState from global;
|
||||
overflow: 'hidden';
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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, { FC } from 'react';
|
||||
import { RenderedElement } from './rendered_element';
|
||||
import { CanvasRenderedPage, CanvasRenderedElement } from '../types';
|
||||
import { useCanvasShareableState } from '../context';
|
||||
|
||||
import css from './page.module.scss';
|
||||
|
||||
interface ComponentProps {
|
||||
/**
|
||||
* The height of the page, in pixels.
|
||||
*/
|
||||
height: number;
|
||||
/**
|
||||
* The width of the page, in pixels.
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* An object describing the Page, taken from a Shareable Workpad.
|
||||
*/
|
||||
page: CanvasRenderedPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Page in the Shareable Workpad is conceptually identical to a Page in a Workpad.
|
||||
*/
|
||||
export const PageComponent: FC<ComponentProps> = ({ page, height, width }) => {
|
||||
const { elements, style, id } = page;
|
||||
|
||||
const output = elements.map((element: CanvasRenderedElement, i) => (
|
||||
<RenderedElement key={element.id} element={element} index={i + 1} />
|
||||
));
|
||||
|
||||
return (
|
||||
<div {...{ id }} className={css.root} style={{ height, width, ...style }}>
|
||||
{output}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The zero-based index of the page relative others within the workpad.
|
||||
*/
|
||||
index: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A store-connected container for the `Page` component.
|
||||
*/
|
||||
export const Page: FC<Props> = ({ index }) => {
|
||||
const [{ workpad }] = useCanvasShareableState();
|
||||
|
||||
if (!workpad) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { height, width, pages } = workpad;
|
||||
const page = pages[index];
|
||||
|
||||
return <PageComponent {...{ page, height, width }} />;
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
:global .kbnCanvas :local .root,
|
||||
:global .kbnCanvas :local .render {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
composes: canvas__element from global;
|
||||
composes: canvasElement from global;
|
||||
}
|
||||
|
||||
.content {
|
||||
composes: canvasElement__content from global;
|
||||
}
|
||||
|
||||
.renderContainer {
|
||||
composes: canvasWorkpad--element_render from global;
|
||||
composes: canvasRenderEl from global;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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, { FC, PureComponent } from 'react';
|
||||
// @ts-ignore Untyped library
|
||||
import Style from 'style-it';
|
||||
// @ts-ignore Untyped local
|
||||
import { Positionable } from '../../public/components/positionable/positionable';
|
||||
// @ts-ignore Untyped local
|
||||
import { elementToShape } from '../../public/components/workpad_page/utils';
|
||||
import { CanvasRenderedElement } from '../types';
|
||||
import { CanvasShareableContext, useCanvasShareableState } from '../context';
|
||||
import { RendererSpec } from '../../types';
|
||||
|
||||
import css from './rendered_element.module.scss';
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
* An object describing the transient, independently renderable Element.
|
||||
*/
|
||||
element: CanvasRenderedElement;
|
||||
|
||||
/**
|
||||
* The index of the Element relative to other Elements on the Page. This is
|
||||
* primarily used for z-indexing.
|
||||
*/
|
||||
index: number;
|
||||
|
||||
/**
|
||||
* The Expression function that evaluates the state of the Element and renders
|
||||
* it to the Page.
|
||||
*/
|
||||
fn: RendererSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Rendered Element is different from an Element added to a Canvas Workpad. A
|
||||
* Rendered Element has actually be evaluated already to gather any data from
|
||||
* datasources, and is just a simple expression to render the result. This
|
||||
* component renders that "transient" element state.
|
||||
*/
|
||||
export class RenderedElementComponent extends PureComponent<Props> {
|
||||
static contextType = CanvasShareableContext;
|
||||
protected ref: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.ref = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { element, fn } = this.props;
|
||||
const { expressionRenderable } = element;
|
||||
const { value } = expressionRenderable;
|
||||
const { as } = value;
|
||||
|
||||
if (!this.ref.current) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: These are stubbed, but may need implementation.
|
||||
fn.render(this.ref.current, value.value, {
|
||||
done: () => {},
|
||||
onDestroy: () => {},
|
||||
onResize: () => {},
|
||||
setFilter: () => {},
|
||||
getFilter: () => '',
|
||||
});
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(as, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
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;
|
||||
const { height, width } = position;
|
||||
|
||||
return (
|
||||
<Positionable height={height} width={width} transformMatrix={shape.transformMatrix}>
|
||||
<div className={css.root}>
|
||||
{Style.it(
|
||||
elementCSS,
|
||||
<div className={css.container} style={{ ...containerStyle }}>
|
||||
<div className={css.content}>
|
||||
<div className={css.renderContainer}>
|
||||
<div key={id} ref={this.ref} data-renderer={as} className={css.render} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Positionable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A store-connected container for the `RenderedElement` component.
|
||||
*/
|
||||
export const RenderedElement: FC<Pick<Props, 'element' | 'index'>> = ({ index, element }) => {
|
||||
const [{ renderers }] = useCanvasShareableState();
|
||||
|
||||
const { expressionRenderable } = element;
|
||||
const { value } = expressionRenderable;
|
||||
const { as } = value;
|
||||
const fn = renderers[as];
|
||||
|
||||
return <RenderedElementComponent {...{ element, fn, index }} />;
|
||||
};
|
13
x-pack/legacy/plugins/canvas/shareable_runtime/constants.d.ts
vendored
Normal file
13
x-pack/legacy/plugins/canvas/shareable_runtime/constants.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 const KIBANA_ROOT: string;
|
||||
export const LIBRARY_NAME: string;
|
||||
export const SHAREABLE_RUNTIME_FILE: string;
|
||||
export const SHAREABLE_RUNTIME_NAME: string;
|
||||
export const SHAREABLE_RUNTIME_OUTPUT: string;
|
||||
export const SHAREABLE_RUNTIME_SRC: string;
|
||||
export const STATS_OUTPUT: string;
|
33
x-pack/legacy/plugins/canvas/shareable_runtime/constants.js
Normal file
33
x-pack/legacy/plugins/canvas/shareable_runtime/constants.js
Normal file
|
@ -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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const LIBRARY_NAME = 'KbnCanvas';
|
||||
const SHAREABLE_RUNTIME_NAME = 'kbn_canvas';
|
||||
const SHAREABLE_RUNTIME_CSS_NAME = 'kbn_canvas_css';
|
||||
const KIBANA_ROOT = path.resolve(__dirname, '../../../../..');
|
||||
const SHAREABLE_RUNTIME_SRC = path.resolve(
|
||||
KIBANA_ROOT,
|
||||
'x-pack/legacy/plugins/canvas/shareable_runtime'
|
||||
);
|
||||
const SHAREABLE_RUNTIME_OUTPUT = path.resolve(SHAREABLE_RUNTIME_SRC, 'build');
|
||||
const SHAREABLE_RUNTIME_FILE = path.resolve(
|
||||
SHAREABLE_RUNTIME_OUTPUT,
|
||||
SHAREABLE_RUNTIME_NAME + '.js'
|
||||
);
|
||||
const STATS_OUTPUT = path.resolve(SHAREABLE_RUNTIME_OUTPUT, 'webpack_stats.json');
|
||||
|
||||
module.exports = {
|
||||
KIBANA_ROOT,
|
||||
LIBRARY_NAME,
|
||||
SHAREABLE_RUNTIME_CSS_NAME,
|
||||
SHAREABLE_RUNTIME_FILE,
|
||||
SHAREABLE_RUNTIME_NAME,
|
||||
SHAREABLE_RUNTIME_OUTPUT,
|
||||
SHAREABLE_RUNTIME_SRC,
|
||||
STATS_OUTPUT,
|
||||
};
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This enumeration applies a strong type to all of the actions that can be
|
||||
* triggered from the interface.
|
||||
*/
|
||||
export enum CanvasShareableActions {
|
||||
SET_WORKPAD = 'SET_WORKPAD',
|
||||
SET_PAGE = 'SET_PAGE',
|
||||
SET_SCRUBBER_VISIBLE = 'SET_SCRUBBER_VISIBLE',
|
||||
SET_AUTOPLAY = 'SET_AUTOPLAY',
|
||||
SET_AUTOPLAY_INTERVAL = 'SET_AUTOPLAY_INTERVAL',
|
||||
SET_TOOLBAR_AUTOHIDE = 'SET_TOOLBAR_AUTOHIDE',
|
||||
}
|
||||
|
||||
interface FluxAction<T, P> {
|
||||
type: T;
|
||||
payload: P;
|
||||
}
|
||||
|
||||
const createAction = <T extends CanvasShareableActions, P>(
|
||||
type: T,
|
||||
payload: P
|
||||
): FluxAction<T, P> => ({
|
||||
type,
|
||||
payload,
|
||||
});
|
||||
|
||||
/**
|
||||
* Set the current page to display
|
||||
* @param page The zero-indexed page to display.
|
||||
*/
|
||||
export const setPageAction = (page: number) =>
|
||||
createAction(CanvasShareableActions.SET_PAGE, { page });
|
||||
|
||||
/**
|
||||
* Set the visibility of the page scrubber.
|
||||
* @param visible True if it should be visible, false otherwise.
|
||||
*/
|
||||
export const setScrubberVisibleAction = (visible: boolean) => {
|
||||
return createAction(CanvasShareableActions.SET_SCRUBBER_VISIBLE, { visible });
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the slides should automatically advance.
|
||||
* @param autoplay True if it should automatically advance, false otherwise.
|
||||
*/
|
||||
export const setAutoplayAction = (isEnabled: boolean) =>
|
||||
createAction(CanvasShareableActions.SET_AUTOPLAY, { isEnabled });
|
||||
|
||||
/**
|
||||
* 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 setAutoplayIntervalAction = (interval: string) =>
|
||||
createAction(CanvasShareableActions.SET_AUTOPLAY_INTERVAL, { interval });
|
||||
|
||||
/**
|
||||
* Set if the toolbar should be hidden if the mouse is not within the bounds of the
|
||||
* Canvas Shareable Workpad.
|
||||
* @param autohide True if the toolbar should hide, false otherwise.
|
||||
*/
|
||||
export const setToolbarAutohideAction = (isAutohide: boolean) =>
|
||||
createAction(CanvasShareableActions.SET_TOOLBAR_AUTOHIDE, { isAutohide });
|
||||
|
||||
const actions = {
|
||||
setPageAction,
|
||||
setScrubberVisibleAction,
|
||||
setAutoplayAction,
|
||||
setAutoplayIntervalAction,
|
||||
setToolbarAutohideAction,
|
||||
};
|
||||
|
||||
/**
|
||||
* Strongly-types the correlation between an `action` and its return.
|
||||
*/
|
||||
export type CanvasShareableAction = ReturnType<typeof actions[keyof typeof actions]>;
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 './state';
|
||||
export * from './actions';
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 { CanvasShareableState } from '../types';
|
||||
import { CanvasShareableAction, CanvasShareableActions } from './actions';
|
||||
|
||||
/**
|
||||
* The Action Reducer for the Shareable Canvas Workpad interface.
|
||||
*/
|
||||
export const reducer = (
|
||||
state: CanvasShareableState,
|
||||
action: CanvasShareableAction
|
||||
): CanvasShareableState => {
|
||||
switch (action.type) {
|
||||
case CanvasShareableActions.SET_PAGE: {
|
||||
const { stage } = state;
|
||||
return {
|
||||
...state,
|
||||
stage: {
|
||||
...stage,
|
||||
page: action.payload.page,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case CanvasShareableActions.SET_SCRUBBER_VISIBLE: {
|
||||
const { footer } = state;
|
||||
|
||||
return {
|
||||
...state,
|
||||
footer: {
|
||||
...footer,
|
||||
isScrubberVisible: action.payload.visible,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case CanvasShareableActions.SET_AUTOPLAY: {
|
||||
const { settings } = state;
|
||||
const { autoplay } = settings;
|
||||
const { isEnabled } = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
settings: {
|
||||
...settings,
|
||||
autoplay: {
|
||||
...autoplay,
|
||||
isEnabled,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case CanvasShareableActions.SET_AUTOPLAY_INTERVAL: {
|
||||
const { settings } = state;
|
||||
const { autoplay } = settings;
|
||||
const { interval } = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
settings: {
|
||||
...settings,
|
||||
autoplay: {
|
||||
...autoplay,
|
||||
interval,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case CanvasShareableActions.SET_TOOLBAR_AUTOHIDE: {
|
||||
const { settings } = state;
|
||||
const { toolbar } = settings;
|
||||
const { isAutohide } = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
settings: {
|
||||
...settings,
|
||||
toolbar: {
|
||||
...toolbar,
|
||||
isAutohide,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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, { createContext, useContext, Dispatch, useReducer, ReactChild } from 'react';
|
||||
import { CanvasShareableState } from '../types';
|
||||
import { reducer } from './reducer';
|
||||
import { CanvasShareableAction } from './actions';
|
||||
|
||||
type StateType = [CanvasShareableState, Dispatch<CanvasShareableAction>];
|
||||
|
||||
/**
|
||||
* The initial state for the Canvas Shareable Runtime.
|
||||
*/
|
||||
export const initialCanvasShareableState: CanvasShareableState = {
|
||||
renderers: {},
|
||||
workpad: null,
|
||||
stage: {
|
||||
page: 0,
|
||||
height: 400,
|
||||
width: 600,
|
||||
},
|
||||
footer: {
|
||||
isScrubberVisible: false,
|
||||
},
|
||||
settings: {
|
||||
autoplay: {
|
||||
isEnabled: false,
|
||||
interval: '5s',
|
||||
},
|
||||
toolbar: {
|
||||
isAutohide: false,
|
||||
},
|
||||
},
|
||||
refs: {
|
||||
stage: React.createRef(),
|
||||
},
|
||||
};
|
||||
|
||||
export const CanvasShareableContext = createContext<StateType>([
|
||||
initialCanvasShareableState,
|
||||
() => {},
|
||||
]);
|
||||
|
||||
export const CanvasShareableStateProvider = ({
|
||||
initialState,
|
||||
children,
|
||||
}: {
|
||||
initialState: CanvasShareableState;
|
||||
children: ReactChild;
|
||||
}) => (
|
||||
<CanvasShareableContext.Provider value={useReducer(reducer, initialState)}>
|
||||
{children}
|
||||
</CanvasShareableContext.Provider>
|
||||
);
|
||||
|
||||
export const useCanvasShareableState = () => useContext<StateType>(CanvasShareableContext);
|
10
x-pack/legacy/plugins/canvas/shareable_runtime/css_modules.d.ts
vendored
Normal file
10
x-pack/legacy/plugins/canvas/shareable_runtime/css_modules.d.ts
vendored
Normal file
|
@ -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 '*.module.scss' {
|
||||
const styles: { [className: string]: string };
|
||||
// eslint-disable-next-line
|
||||
export default styles;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue