[7.x] [Canvas] Expression repeat image (#104255) (#106638)

* [Canvas] Expression repeat image (#104255)

* Repeat Image plugin added.
# Conflicts:
#	packages/kbn-optimizer/limits.yml
#	x-pack/plugins/translations/translations/ja-JP.json
#	x-pack/plugins/translations/translations/zh-CN.json

* Removed not defined plugin `userSetup`.
This commit is contained in:
Yaroslav Kuznietsov 2021-07-23 14:10:00 +03:00 committed by GitHub
parent f276d0732a
commit 17933a559c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 666 additions and 322 deletions

View file

@ -18,6 +18,7 @@
"devTools": "src/plugins/dev_tools",
"expressions": "src/plugins/expressions",
"expressionError": "src/plugins/expression_error",
"expressionRepeatImage": "src/plugins/expression_repeat_image",
"expressionRevealImage": "src/plugins/expression_reveal_image",
"expressionShape": "src/plugins/expression_shape",
"inputControl": "src/plugins/input_control_vis",

View file

@ -74,6 +74,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a
|Expression Error plugin adds an error renderer to the expression plugin. The renderer will display the error image.
|{kib-repo}blob/{branch}/src/plugins/expression_repeat_image/README.md[expressionRepeatImage]
|Expression Repeat Image plugin adds a repeatImage function to the expression plugin and an associated renderer. The renderer will display the given image in mutliple instances.
|{kib-repo}blob/{branch}/src/plugins/expression_reveal_image/README.md[expressionRevealImage]
|Expression Reveal Image plugin adds a revealImage function to the expression plugin and an associated renderer. The renderer will display the given percentage of a given image.

View file

@ -113,4 +113,5 @@ pageLoadAssetSize:
expressionRevealImage: 25675
cases: 144442
expressionError: 22127
expressionRepeatImage: 22341
expressionShape: 30033

View file

@ -18,6 +18,7 @@ export const storybookAliases = {
data_enhanced: 'x-pack/plugins/data_enhanced/.storybook',
embeddable: 'src/plugins/embeddable/.storybook',
expression_error: 'src/plugins/expression_error/.storybook',
expression_repeat_image: 'src/plugins/expression_repeat_image/.storybook',
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
expression_shape: 'src/plugins/expression_shape/.storybook',
infra: 'x-pack/plugins/infra/.storybook',

View file

@ -12,6 +12,12 @@ import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import { ShowDebugging } from './show_debugging';
export interface Props {
payload: {
error: Error;
};
}
const strings = {
getDescription: () =>
i18n.translate('expressionError.errorComponent.description', {
@ -23,12 +29,6 @@ const strings = {
}),
};
export interface Props {
payload: {
error: Error;
};
}
export const Error: FC<Props> = ({ payload }) => {
const message = get(payload, 'error.message');

View 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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
// eslint-disable-next-line import/no-commonjs
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -0,0 +1,9 @@
# expressionRepeatImage
Expression Repeat Image plugin adds a `repeatImage` function to the expression plugin and an associated renderer. The renderer will display the given image in mutliple instances.
---
## Development
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment.

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const PLUGIN_ID = 'expressionRepeatImage';
export const PLUGIN_NAME = 'expressionRepeatImage';
export const CONTEXT = '_context_';
export const BASE64 = '`base64`';
export const URL = 'URL';

View 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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { repeatImageFunction } from './repeat_image_function';
export const functions = [repeatImageFunction];
export { repeatImageFunction };

View file

@ -1,29 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExecutionContext } from 'src/plugins/expressions';
import {
getElasticLogo,
getElasticOutline,
functionWrapper,
} from '../../../../../../src/plugins/presentation_util/common/lib';
import { repeatImage } from './repeat_image';
} from '../../../presentation_util/common/lib';
import { repeatImageFunction } from './repeat_image_function';
describe('repeatImage', () => {
const fn = functionWrapper(repeatImage);
const fn = functionWrapper(repeatImageFunction);
let elasticLogo;
let elasticOutline;
let elasticLogo: string;
let elasticOutline: string;
beforeEach(async () => {
elasticLogo = await (await getElasticLogo()).elasticLogo;
elasticOutline = await (await getElasticOutline()).elasticOutline;
});
it('returns a render as repeatImage', async () => {
const result = await fn(10);
const result = await fn(10, {}, {} as ExecutionContext);
expect(result).toHaveProperty('type', 'render');
expect(result).toHaveProperty('as', 'repeatImage');
});
@ -31,46 +33,47 @@ describe('repeatImage', () => {
describe('args', () => {
describe('image', () => {
it('sets the source of the repeated image', async () => {
const result = (await fn(10, { image: elasticLogo })).value;
const result = (await fn(10, { image: elasticLogo }, {} as ExecutionContext)).value;
expect(result).toHaveProperty('image', elasticLogo);
});
it('defaults to the Elastic outline logo', async () => {
const result = (await fn(100000)).value;
const result = (await fn(100000, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('image', elasticOutline);
});
});
describe('size', () => {
it('sets the size of the image', async () => {
const result = (await fn(-5, { size: 200 })).value;
const result = (await fn(-5, { size: 200 }, {} as ExecutionContext)).value;
expect(result).toHaveProperty('size', 200);
});
it('defaults to 100', async () => {
const result = (await fn(-5)).value;
const result = (await fn(-5, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('size', 100);
});
});
describe('max', () => {
it('sets the maximum number of a times the image is repeated', async () => {
const result = (await fn(100000, { max: 20 })).value;
const result = (await fn(100000, { max: 20 }, {} as ExecutionContext)).value;
expect(result).toHaveProperty('max', 20);
});
it('defaults to 1000', async () => {
const result = (await fn(100000)).value;
const result = (await fn(100000, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('max', 1000);
});
});
describe('emptyImage', () => {
it('returns repeatImage object with emptyImage as undefined', async () => {
const result = (await fn(100000, { emptyImage: elasticLogo })).value;
const result = (await fn(100000, { emptyImage: elasticLogo }, {} as ExecutionContext))
.value;
expect(result).toHaveProperty('emptyImage', elasticLogo);
});
it('sets emptyImage to null', async () => {
const result = (await fn(100000)).value;
const result = (await fn(100000, {}, {} as ExecutionContext)).value;
expect(result).toHaveProperty('emptyImage', null);
});
});

View file

@ -0,0 +1,115 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import {
getElasticOutline,
isValidUrl,
resolveWithMissingImage,
} from '../../../presentation_util/common/lib';
import { CONTEXT, BASE64, URL } from '../constants';
import { ExpressionRepeatImageFunction } from '../types';
export const strings = {
help: i18n.translate('expressionRepeatImage.functions.repeatImageHelpText', {
defaultMessage: 'Configures a repeating image element.',
}),
args: {
emptyImage: i18n.translate(
'expressionRepeatImage.functions.repeatImage.args.emptyImageHelpText',
{
defaultMessage:
'Fills the difference between the {CONTEXT} and {maxArg} parameter for the element with this image. ' +
'Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
CONTEXT,
maxArg: '`max`',
URL,
},
}
),
image: i18n.translate('expressionRepeatImage.functions.repeatImage.args.imageHelpText', {
defaultMessage:
'The image to repeat. Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
URL,
},
}),
max: i18n.translate('expressionRepeatImage.functions.repeatImage.args.maxHelpText', {
defaultMessage: 'The maximum number of times the image can repeat.',
}),
size: i18n.translate('expressionRepeatImage.functions.repeatImage.args.sizeHelpText', {
defaultMessage:
'The maximum height or width of the image, in pixels. ' +
'When the image is taller than it is wide, this function limits the height.',
}),
},
};
const errors = {
getMissingMaxArgumentErrorMessage: () =>
i18n.translate('expressionRepeatImage.error.repeatImage.missingMaxArgument', {
defaultMessage: '{maxArgument} must be set if providing an {emptyImageArgument}',
values: {
maxArgument: '`max`',
emptyImageArgument: '`emptyImage`',
},
}),
};
export const repeatImageFunction: ExpressionRepeatImageFunction = () => {
const { help, args: argHelp } = strings;
return {
name: 'repeatImage',
aliases: [],
type: 'render',
inputTypes: ['number'],
help,
args: {
emptyImage: {
types: ['string', 'null'],
help: argHelp.emptyImage,
default: null,
},
image: {
types: ['string', 'null'],
help: argHelp.image,
default: null,
},
max: {
types: ['number', 'null'],
help: argHelp.max,
default: 1000,
},
size: {
types: ['number'],
default: 100,
help: argHelp.size,
},
},
fn: async (count, args) => {
if (args.emptyImage !== null && isValidUrl(args.emptyImage) && args.max === null) {
throw new Error(errors.getMissingMaxArgumentErrorMessage());
}
const { elasticOutline } = await getElasticOutline();
return {
type: 'render',
as: 'repeatImage',
value: {
count: Math.floor(count),
...args,
image: resolveWithMissingImage(args.image, elasticOutline),
emptyImage: resolveWithMissingImage(args.emptyImage),
},
};
},
};
};

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './constants';
export * from './types';
export * from './expression_functions';

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExpressionFunctionDefinition, ExpressionValueRender } from '../../../expressions';
interface Arguments {
image: string | null;
size: number;
max: number | null;
emptyImage: string | null;
}
export interface Return {
count: number;
image: string;
size: number;
max: number;
emptyImage: string | null;
}
export type ExpressionRepeatImageFunction = () => ExpressionFunctionDefinition<
'repeatImage',
number,
Arguments,
Promise<ExpressionValueRender<Arguments>>
>;

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export type OriginString = 'bottom' | 'left' | 'top' | 'right';
export interface RepeatImageRendererConfig {
max: number;
count: number;
emptyImage: string;
image: string;
size: number;
}
export interface NodeDimensions {
width: number;
height: number;
}

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './expression_functions';
export * from './expression_renderers';

View 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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/src/plugins/expression_repeat_image'],
};

View file

@ -0,0 +1,10 @@
{
"id": "expressionRepeatImage",
"version": "1.0.0",
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["expressions", "presentationUtil"],
"optionalPlugins": [],
"requiredBundles": []
}

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './repeat_image_component';

View file

@ -0,0 +1,99 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { ReactElement, useEffect, useState } from 'react';
import { times } from 'lodash';
import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { RepeatImageRendererConfig } from '../../common';
interface RepeatImageComponentProps extends RepeatImageRendererConfig {
onLoaded: IInterpreterRenderHandlers['done'];
parentNode: HTMLElement;
}
interface LoadedImages {
image: HTMLImageElement | null;
emptyImage: HTMLImageElement | null;
}
async function loadImage(src: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (error) => reject(error);
img.src = src;
});
}
async function loadImages(images: string[]): Promise<Array<HTMLImageElement | null>> {
const results = await Promise.allSettled([...images.map(loadImage)]);
return results.map((loadedImage) =>
loadedImage.status === 'rejected' ? null : loadedImage.value
);
}
function setImageSize(img: HTMLImageElement, size: number) {
if (img.naturalHeight > img.naturalWidth) {
img.height = size;
} else {
img.width = size;
}
}
function createImageJSX(img: HTMLImageElement | null) {
if (!img) return null;
const params = img.width > img.height ? { heigth: img.height } : { width: img.width };
return <img src={img.src} {...params} alt="" />;
}
function RepeatImageComponent({
max,
count,
emptyImage: emptyImageSrc,
image: imageSrc,
size,
onLoaded,
}: RepeatImageComponentProps) {
const [images, setImages] = useState<LoadedImages>({
image: null,
emptyImage: null,
});
useEffect(() => {
loadImages([imageSrc, emptyImageSrc]).then((result) => {
const [image, emptyImage] = result;
setImages({ image, emptyImage });
onLoaded();
});
}, [imageSrc, emptyImageSrc, onLoaded]);
const imagesToRender: Array<ReactElement | null> = [];
const { image, emptyImage } = images;
if (max && count > max) count = max;
if (image) {
setImageSize(image, size);
times(count, () => imagesToRender.push(createImageJSX(image)));
}
if (emptyImage) {
setImageSize(emptyImage, size);
times(max - count, () => imagesToRender.push(createImageJSX(emptyImage)));
}
return (
<div className="repeatImage" style={{ pointerEvents: 'none' }}>
{imagesToRender}
</div>
);
}
// default export required for React.Lazy
// eslint-disable-next-line import/no-default-export
export { RepeatImageComponent as default };

View file

@ -1,19 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { storiesOf } from '@storybook/react';
import { repeatImage } from '../repeat_image';
import { Render } from '../../../../presentation_util/public/__stories__';
import { repeatImageRenderer } from '../repeat_image_renderer';
import {
getElasticLogo,
getElasticOutline,
} from '../../../../../../src/plugins/presentation_util/common/lib';
import { waitFor } from '../../../../../../src/plugins/presentation_util/public/__stories__';
import { Render } from './render';
const Renderer = ({
elasticLogo,
@ -30,7 +31,7 @@ const Renderer = ({
emptyImage: elasticOutline,
};
return <Render renderer={repeatImage} config={config} width="400px" />;
return <Render renderer={repeatImageRenderer} config={config} width="400px" />;
};
storiesOf('enderers/repeatImage', module).add(

View 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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { repeatImageRenderer } from './repeat_image_renderer';
export const renderers = [repeatImageRenderer];
export { repeatImageRenderer };

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { lazy } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { i18n } from '@kbn/i18n';
import { getElasticOutline, isValidUrl, withSuspense } from '../../../presentation_util/public';
import { RepeatImageRendererConfig } from '../../common/types';
const strings = {
getDisplayName: () =>
i18n.translate('expressionRepeatImage.renderer.repeatImage.displayName', {
defaultMessage: 'RepeatImage',
}),
getHelpDescription: () =>
i18n.translate('expressionRepeatImage.renderer.repeatImage.helpDescription', {
defaultMessage: 'Render a basic repeatImage',
}),
};
const LazyRepeatImageComponent = lazy(() => import('../components/repeat_image_component'));
const RepeatImageComponent = withSuspense(LazyRepeatImageComponent, null);
export const repeatImageRenderer = (): ExpressionRenderDefinition<RepeatImageRendererConfig> => ({
name: 'repeatImage',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render: async (
domNode: HTMLElement,
config: RepeatImageRendererConfig,
handlers: IInterpreterRenderHandlers
) => {
const { elasticOutline } = await getElasticOutline();
const settings = {
...config,
image: isValidUrl(config.image) ? config.image : elasticOutline,
emptyImage: config.emptyImage || '',
};
handlers.onDestroy(() => {
unmountComponentAtNode(domNode);
});
render(
<I18nProvider>
<RepeatImageComponent onLoaded={handlers.done} {...settings} parentNode={domNode} />
</I18nProvider>,
domNode
);
},
});

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExpressionRepeatImagePlugin } from './plugin';
export type { ExpressionRepeatImagePluginSetup, ExpressionRepeatImagePluginStart } from './plugin';
export function plugin() {
return new ExpressionRepeatImagePlugin();
}
export * from './expression_renderers';

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public';
import { repeatImageFunction } from '../common/expression_functions';
import { repeatImageRenderer } from './expression_renderers';
interface SetupDeps {
expressions: ExpressionsSetup;
}
interface StartDeps {
expression: ExpressionsStart;
}
export type ExpressionRepeatImagePluginSetup = void;
export type ExpressionRepeatImagePluginStart = void;
export class ExpressionRepeatImagePlugin
implements
Plugin<
ExpressionRepeatImagePluginSetup,
ExpressionRepeatImagePluginStart,
SetupDeps,
StartDeps
> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRepeatImagePluginSetup {
expressions.registerFunction(repeatImageFunction);
expressions.registerRenderer(repeatImageRenderer);
}
public start(core: CoreStart): ExpressionRepeatImagePluginStart {}
public stop() {}
}

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { ExpressionRepeatImagePlugin } from './plugin';
export type { ExpressionRepeatImagePluginSetup, ExpressionRepeatImagePluginStart } from './plugin';
export function plugin() {
return new ExpressionRepeatImagePlugin();
}

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server';
import { repeatImageFunction } from '../common';
interface SetupDeps {
expressions: ExpressionsServerSetup;
}
interface StartDeps {
expression: ExpressionsServerStart;
}
export type ExpressionRepeatImagePluginSetup = void;
export type ExpressionRepeatImagePluginStart = void;
export class ExpressionRepeatImagePlugin
implements
Plugin<
ExpressionRepeatImagePluginSetup,
ExpressionRepeatImagePluginStart,
SetupDeps,
StartDeps
> {
public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionRepeatImagePluginSetup {
expressions.registerFunction(repeatImageFunction);
}
public start(core: CoreStart): ExpressionRepeatImagePluginStart {}
public stop() {}
}

View file

@ -0,0 +1,21 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true,
"isolatedModules": true
},
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
],
"references": [
{ "path": "../../core/tsconfig.json" },
{ "path": "../presentation_util/tsconfig.json" },
{ "path": "../expressions/tsconfig.json" },
]
}

View file

@ -42,7 +42,6 @@ import { render } from './render';
import { replace } from './replace';
import { rounddate } from './rounddate';
import { rowCount } from './rowCount';
import { repeatImage } from './repeat_image';
import { seriesStyle } from './seriesStyle';
import { sort } from './sort';
import { staticColumn } from './staticColumn';
@ -90,7 +89,6 @@ export const functions = [
ply,
progress,
render,
repeatImage,
replace,
rounddate,
rowCount,

View file

@ -1,84 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import {
getElasticOutline,
resolveWithMissingImage,
} from '../../../../../../src/plugins/presentation_util/common/lib';
import { Render } from '../../../types';
import { getFunctionHelp } from '../../../i18n';
interface Arguments {
image: string | null;
size: number;
max: number;
emptyImage: string | null;
}
export interface Return {
count: number;
image: string;
size: number;
max: number;
emptyImage: string | null;
}
export function repeatImage(): ExpressionFunctionDefinition<
'repeatImage',
number,
Arguments,
Promise<Render<Arguments>>
> {
const { help, args: argHelp } = getFunctionHelp().repeatImage;
return {
name: 'repeatImage',
aliases: [],
type: 'render',
inputTypes: ['number'],
help,
args: {
emptyImage: {
types: ['string', 'null'],
help: argHelp.emptyImage,
default: null,
},
image: {
types: ['string', 'null'],
help: argHelp.image,
default: null,
},
max: {
types: ['number'],
help: argHelp.max,
default: 1000,
},
size: {
types: ['number'],
default: 100,
help: argHelp.size,
},
},
fn: async (count, args) => {
const { elasticOutline } = await getElasticOutline();
if (args.image === null) {
args.image = elasticOutline;
}
return {
type: 'render',
as: 'repeatImage',
value: {
count: Math.floor(count),
...args,
image: resolveWithMissingImage(args.image, elasticOutline),
emptyImage: resolveWithMissingImage(args.emptyImage),
},
};
},
};
}

View file

@ -11,20 +11,9 @@ import { metric } from './metric';
import { pie } from './pie';
import { plot } from './plot';
import { progress } from './progress';
import { repeatImage } from './repeat_image';
import { table } from './table';
import { text } from './text';
import { table } from './table';
export const renderFunctions = [
image,
markdown,
metric,
pie,
plot,
progress,
repeatImage,
table,
text,
];
export const renderFunctions = [image, markdown, metric, pie, plot, progress, table, text];
export const renderFunctionFactories = [];

View file

@ -5,9 +5,17 @@
* 2.0.
*/
import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public';
import { errorRenderer, debugRenderer } from '../../../../../src/plugins/expression_error/public';
import { repeatImageRenderer } from '../../../../../src/plugins/expression_repeat_image/public';
import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public';
import { shapeRenderer } from '../../../../../src/plugins/expression_shape/public';
export const renderFunctions = [revealImageRenderer, debugRenderer, errorRenderer, shapeRenderer];
export const renderFunctions = [
revealImageRenderer,
debugRenderer,
errorRenderer,
shapeRenderer,
repeatImageRenderer,
];
export const renderFunctionFactories = [];

View file

@ -1,79 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import $ from 'jquery';
import { times } from 'lodash';
import {
getElasticOutline,
isValidUrl,
} from '../../../../../src/plugins/presentation_util/common/lib';
import { RendererStrings, ErrorStrings } from '../../i18n';
import { Return as Arguments } from '../functions/common/repeat_image';
import { RendererFactory } from '../../types';
const { repeatImage: strings } = RendererStrings;
const { RepeatImage: errors } = ErrorStrings;
export const repeatImage: RendererFactory<Arguments> = () => ({
name: 'repeatImage',
displayName: strings.getDisplayName(),
help: strings.getHelpDescription(),
reuseDomNode: true,
render: async (domNode, config, handlers) => {
let image = config.image;
if (!isValidUrl(config.image)) {
image = (await getElasticOutline()).elasticOutline;
}
const settings = {
...config,
image,
emptyImage: config.emptyImage || '',
};
const container = $('<div class="repeatImage" style="pointer-events: none;">');
function setSize(img: HTMLImageElement) {
if (img.naturalHeight > img.naturalWidth) {
img.height = settings.size;
} else {
img.width = settings.size;
}
}
function finish() {
$(domNode).append(container);
handlers.done();
}
const img = new Image();
img.onload = function () {
setSize(img);
if (settings.max && settings.count > settings.max) {
settings.count = settings.max;
}
times(settings.count, () => container.append($(img).clone()));
if (isValidUrl(settings.emptyImage)) {
if (settings.max == null) {
throw new Error(errors.getMissingMaxArgumentErrorMessage());
}
const emptyImage = new Image();
emptyImage.onload = function () {
setSize(emptyImage);
times(settings.max - settings.count, () => container.append($(emptyImage).clone()));
finish();
};
emptyImage.src = settings.emptyImage;
} else {
finish();
}
};
img.src = settings.image;
},
});

View file

@ -14,18 +14,20 @@ import { ArgumentStrings } from '../../../i18n';
const { Shape: strings } = ArgumentStrings;
const ShapeArgInput = ({ onValueChange, argValue, typeInstance }) => (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<ShapePickerPopover
value={argValue}
onChange={onValueChange}
shapes={typeInstance.options.shapes}
ariaLabel={typeInstance.displayName}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
const ShapeArgInput = ({ onValueChange, argValue, typeInstance }) => {
return (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<ShapePickerPopover
value={argValue}
onChange={onValueChange}
shapes={typeInstance.options.shapes}
ariaLabel={typeInstance.displayName}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};
ShapeArgInput.propTypes = {
argValue: PropTypes.any.isRequired,

View file

@ -58,16 +58,6 @@ export const ErrorStrings = {
},
}),
},
RepeatImage: {
getMissingMaxArgumentErrorMessage: () =>
i18n.translate('xpack.canvas.error.repeatImage.missingMaxArgument', {
defaultMessage: '{maxArgument} must be set if providing an {emptyImageArgument}',
values: {
maxArgument: '`max`',
emptyImageArgument: '`emptyImage`',
},
}),
},
WorkpadDropzone: {
getTooManyFilesErrorMessage: () =>
i18n.translate('xpack.canvas.error.workpadDropzone.tooManyFilesErrorMessage', {

View file

@ -1,47 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { repeatImage } from '../../../canvas_plugin_src/functions/common/repeat_image';
import { FunctionHelp } from '../function_help';
import { FunctionFactory } from '../../../types';
import { CONTEXT, BASE64, URL } from '../../constants';
export const help: FunctionHelp<FunctionFactory<typeof repeatImage>> = {
help: i18n.translate('xpack.canvas.functions.repeatImageHelpText', {
defaultMessage: 'Configures a repeating image element.',
}),
args: {
emptyImage: i18n.translate('xpack.canvas.functions.repeatImage.args.emptyImageHelpText', {
defaultMessage:
'Fills the difference between the {CONTEXT} and {maxArg} parameter for the element with this image. ' +
'Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
CONTEXT,
maxArg: '`max`',
URL,
},
}),
image: i18n.translate('xpack.canvas.functions.repeatImage.args.imageHelpText', {
defaultMessage:
'The image to repeat. Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.',
values: {
BASE64,
URL,
},
}),
max: i18n.translate('xpack.canvas.functions.repeatImage.args.maxHelpText', {
defaultMessage: 'The maximum number of times the image can repeat.',
}),
size: i18n.translate('xpack.canvas.functions.repeatImage.args.sizeHelpText', {
defaultMessage:
'The maximum height or width of the image, in pixels. ' +
'When the image is taller than it is wide, this function limits the height.',
}),
},
};

View file

@ -55,7 +55,6 @@ import { help as ply } from './dict/ply';
import { help as pointseries } from './dict/pointseries';
import { help as progress } from './dict/progress';
import { help as render } from './dict/render';
import { help as repeatImage } from './dict/repeat_image';
import { help as replace } from './dict/replace';
import { help as rounddate } from './dict/rounddate';
import { help as rowCount } from './dict/row_count';
@ -214,7 +213,6 @@ export const getFunctionHelp = (): FunctionHelpDict => ({
pointseries,
progress,
render,
repeatImage,
replace,
rounddate,
rowCount,

View file

@ -119,16 +119,6 @@ export const RendererStrings = {
defaultMessage: 'Render a progress indicator that reveals a percentage of an element',
}),
},
repeatImage: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.repeatImage.displayName', {
defaultMessage: 'Image repeat',
}),
getHelpDescription: () =>
i18n.translate('xpack.canvas.renderer.repeatImage.helpDescription', {
defaultMessage: 'Repeat an image a given number of times',
}),
},
table: {
getDisplayName: () =>
i18n.translate('xpack.canvas.renderer.table.displayName', {

View file

@ -11,9 +11,10 @@
"data",
"embeddable",
"expressionError",
"expressionRepeatImage",
"expressionRevealImage",
"expressions",
"expressionShape",
"expressions",
"features",
"inspector",
"presentationUtil",

View file

@ -7,6 +7,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { i18n } from '@kbn/i18n';
import {
EuiModal,
EuiModalBody,
@ -18,8 +19,6 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { withSuspense } from '../../../../../../../src/plugins/presentation_util/public';
import { LazyErrorComponent } from '../../../../../../../src/plugins/expression_error/public';
import { Datatable } from '../../datatable';

View file

@ -84,12 +84,12 @@ export const RenderWithFn: FC<Props> = ({
[]
);
const render = useCallback(() => {
const render = useCallback(async () => {
if (!isEqual(handlers.current, incomingHandlers)) {
handlers.current = incomingHandlers;
}
renderFn(renderTarget.current!, config, handlers.current);
await renderFn(renderTarget.current!, config, handlers.current);
}, [renderTarget, config, renderFn, incomingHandlers]);
useEffect(() => {
@ -101,12 +101,13 @@ export const RenderWithFn: FC<Props> = ({
resetRenderTarget();
}
try {
render();
firstRender.current = false;
} catch (err: any) {
onError(err, { title: strings.getRenderErrorMessage(functionName) });
}
render()
.then(() => {
firstRender.current = false;
})
.catch((err) => {
onError(err, { title: strings.getRenderErrorMessage(functionName) });
});
}, [domNode, functionName, onError, render, resetRenderTarget, reuseNode]);
return (

View file

@ -18,7 +18,7 @@ interface Props {
export const ShapePicker: FC<Props> = ({ shapes, onChange = () => {} }) => (
<EuiFlexGrid gutterSize="s" columns={4} className="canvasShapePicker">
{shapes.sort().map((shapeKey) => (
{shapes.sort().map((shapeKey: string) => (
<EuiFlexItem key={shapeKey}>
<EuiLink onClick={() => onChange(shapeKey)}>
<ShapePreview shape={shapeKey} />

View file

@ -9,7 +9,6 @@ import React, { FC, RefCallback, useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import {
LazyShapeDrawer,
Shape,
ShapeDrawerComponentProps,
getDefaultShapeData,
SvgConfig,
@ -19,7 +18,7 @@ import {
import { withSuspense } from '../../../../../../src/plugins/presentation_util/public';
interface Props {
shape?: Shape;
shape?: string;
}
const ShapeDrawer = withSuspense<ShapeDrawerComponentProps, ShapeRef>(LazyShapeDrawer);

View file

@ -6,7 +6,6 @@
*/
import { image } from '../canvas_plugin_src/renderers/image';
import { repeatImage } from '../canvas_plugin_src/renderers/repeat_image';
import { markdown } from '../canvas_plugin_src/renderers/markdown';
import { metric } from '../canvas_plugin_src/renderers/metric';
import { pie } from '../canvas_plugin_src/renderers/pie';
@ -14,11 +13,12 @@ import { plot } from '../canvas_plugin_src/renderers/plot';
import { progress } from '../canvas_plugin_src/renderers/progress';
import { table } from '../canvas_plugin_src/renderers/table';
import { text } from '../canvas_plugin_src/renderers/text';
import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public';
import {
errorRenderer as error,
debugRenderer as debug,
} from '../../../../src/plugins/expression_error/public';
import { repeatImageRenderer as repeatImage } from '../../../../src/plugins/expression_repeat_image/public';
import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public';
import { shapeRenderer as shape } from '../../../../src/plugins/expression_shape/public';
/**

View file

@ -32,6 +32,7 @@
{ "path": "../../../src/plugins/embeddable/tsconfig.json" },
{ "path": "../../../src/plugins/expressions/tsconfig.json" },
{ "path": "../../../src/plugins/expression_error/tsconfig.json" },
{ "path": "../../../src/plugins/expression_repeat_image/tsconfig.json" },
{ "path": "../../../src/plugins/expression_reveal_image/tsconfig.json" },
{ "path": "../../../src/plugins/expression_shape/tsconfig.json" },
{ "path": "../../../src/plugins/home/tsconfig.json" },

View file

@ -6061,7 +6061,7 @@
"xpack.canvas.error.esService.fieldsFetchErrorMessage": "「{index}」の Elasticsearch フィールドを取得できませんでした",
"xpack.canvas.error.esService.indicesFetchErrorMessage": "Elasticsearch インデックスを取得できませんでした",
"xpack.canvas.error.RenderWithFn.renderErrorMessage": "「{functionName}」のレンダリングが失敗しました",
"xpack.canvas.error.repeatImage.missingMaxArgument": "{emptyImageArgument} を指定する場合は、{maxArgument} を設定する必要があります",
"expressionRepeatImage.error.repeatImage.missingMaxArgument": "{emptyImageArgument} を指定する場合は、{maxArgument} を設定する必要があります",
"xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage": "ワークパッドのクローンを作成できませんでした",
"xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage": "ワークパッドをアップロードできませんでした",
"xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage": "すべてのワークパッドを削除できませんでした",
@ -6304,11 +6304,11 @@
"xpack.canvas.functions.render.args.containerStyleHelpText": "背景、境界、透明度を含む、コンテナーのスタイルです。",
"xpack.canvas.functions.render.args.cssHelpText": "このエレメントの対象となるカスタム {CSS} のブロックです。",
"xpack.canvas.functions.renderHelpText": "{CONTEXT}を特定のエレメントとしてレンダリングし、背景と境界のスタイルなどのエレメントレベルのオプションを設定します。",
"xpack.canvas.functions.repeatImage.args.emptyImageHelpText": "この画像のエレメントについて、{CONTEXT}および{maxArg}パラメーターの差異を解消します。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
"xpack.canvas.functions.repeatImage.args.imageHelpText": "繰り返す画像です。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
"xpack.canvas.functions.repeatImage.args.maxHelpText": "画像が繰り返される最高回数です。",
"xpack.canvas.functions.repeatImage.args.sizeHelpText": "画像の高さまたは幅のピクセル単位での最高値です。画像が縦長の場合、この関数は高さを制限します。",
"xpack.canvas.functions.repeatImageHelpText": "繰り返し画像エレメントを構成します。",
"expressionRepeatImage.functions.repeatImage.args.emptyImageHelpText": "この画像のエレメントについて、{CONTEXT}および{maxArg}パラメーターの差異を解消します。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
"expressionRepeatImage.functions.repeatImage.args.imageHelpText": "繰り返す画像です。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
"expressionRepeatImage.functions.repeatImage.args.maxHelpText": "画像が繰り返される最高回数です。",
"expressionRepeatImage.functions.repeatImage.args.sizeHelpText": "画像の高さまたは幅のピクセル単位での最高値です。画像が縦長の場合、この関数は高さを制限します。",
"expressionRepeatImage.functions.repeatImageHelpText": "繰り返し画像エレメントを構成します。",
"xpack.canvas.functions.replace.args.flagsHelpText": "フラグを指定します。{url}を参照してください。",
"xpack.canvas.functions.replace.args.patternHelpText": "{JS} 正規表現のテキストまたはパターンです。例:{example}。ここではキャプチャグループを使用できます。",
"xpack.canvas.functions.replace.args.replacementHelpText": "文字列の一致する部分の代わりです。キャプチャグループはノードによってアクセス可能です。例:{example}。",
@ -6505,8 +6505,8 @@
"xpack.canvas.renderer.plot.helpDescription": "データから XY プロットをレンダリングします",
"xpack.canvas.renderer.progress.displayName": "進捗インジケーター",
"xpack.canvas.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします",
"xpack.canvas.renderer.repeatImage.displayName": "画像の繰り返し",
"xpack.canvas.renderer.repeatImage.helpDescription": "画像を指定回数繰り返し表示します",
"expressionRepeatImage.renderer.repeatImage.displayName": "画像の繰り返し",
"expressionRepeatImage.renderer.repeatImage.helpDescription": "画像を指定回数繰り返し表示します",
"expressionShape.renderer.shape.displayName": "形状",
"expressionShape.renderer.shape.helpDescription": "基本的な図形をレンダリングします",
"xpack.canvas.renderer.table.displayName": "データテーブル",

View file

@ -6097,7 +6097,7 @@
"xpack.canvas.error.esService.fieldsFetchErrorMessage": "无法为“{index}”提取 Elasticsearch 字段",
"xpack.canvas.error.esService.indicesFetchErrorMessage": "无法提取 Elasticsearch 索引",
"xpack.canvas.error.RenderWithFn.renderErrorMessage": "呈现“{functionName}”失败。",
"xpack.canvas.error.repeatImage.missingMaxArgument": "如果提供 {emptyImageArgument},则必须设置 {maxArgument}",
"expressionRepeatImage.error.repeatImage.missingMaxArgument": "如果提供 {emptyImageArgument},则必须设置 {maxArgument}",
"xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage": "无法克隆 Workpad",
"xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage": "无法上传 Workpad",
"xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage": "无法删除所有 Workpad",
@ -6341,11 +6341,11 @@
"xpack.canvas.functions.render.args.containerStyleHelpText": "容器的样式,包括背景、边框和透明度。",
"xpack.canvas.functions.render.args.cssHelpText": "要限定于元素的任何定制 {CSS} 块。",
"xpack.canvas.functions.renderHelpText": "将 {CONTEXT} 呈现为特定元素,并设置元素级别选项,例如背景和边框样式。",
"xpack.canvas.functions.repeatImage.args.emptyImageHelpText": "使用此图像填充元素的 {CONTEXT} 和 {maxArg} 参数之间的差距。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。",
"xpack.canvas.functions.repeatImage.args.imageHelpText": "要重复的图像。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。",
"xpack.canvas.functions.repeatImage.args.maxHelpText": "图像可以重复的最大次数。",
"xpack.canvas.functions.repeatImage.args.sizeHelpText": "图像的最大高度或宽度,以像素为单位。图像的高大于宽时,此函数将限制高度。",
"xpack.canvas.functions.repeatImageHelpText": "配置重复图像元素。",
"expressionRepeatImage.functions.repeatImage.args.emptyImageHelpText": "使用此图像填充元素的 {CONTEXT} 和 {maxArg} 参数之间的差距。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。",
"expressionRepeatImage.functions.repeatImage.args.imageHelpText": "要重复的图像。以 {BASE64} 数据 {URL} 的形式提供图像资产或传入子表达式。",
"expressionRepeatImage.functions.repeatImage.args.maxHelpText": "图像可以重复的最大次数。",
"expressionRepeatImage.functions.repeatImage.args.sizeHelpText": "图像的最大高度或宽度,以像素为单位。图像的高大于宽时,此函数将限制高度。",
"expressionRepeatImage.functions.repeatImageHelpText": "配置重复图像元素。",
"xpack.canvas.functions.replace.args.flagsHelpText": "指定标志。请参见 {url}。",
"xpack.canvas.functions.replace.args.patternHelpText": "{JS} 正则表达式的文本或模式。例如,{example}。您可以在此处使用捕获组。",
"xpack.canvas.functions.replace.args.replacementHelpText": "字符串匹配部分的替代。捕获组可以通过其索引进行访问。例如,{example}。",
@ -6542,10 +6542,12 @@
"xpack.canvas.renderer.plot.helpDescription": "根据您的数据呈现 XY 坐标图",
"xpack.canvas.renderer.progress.displayName": "进度指示",
"xpack.canvas.renderer.progress.helpDescription": "呈现显示元素百分比的进度指示",
"xpack.canvas.renderer.repeatImage.displayName": "图像重复",
"xpack.canvas.renderer.repeatImage.helpDescription": "重复图像给定次数",
"expressionShape.renderer.shape.displayName": "形状",
"expressionShape.renderer.shape.helpDescription": "呈现基本形状",
"expressionRepeatImage.renderer.repeatImage.displayName": "图像重复",
"expressionRepeatImage.renderer.repeatImage.helpDescription": "重复图像给定次数",
"expressionRevealImage.renderer.revealImage.displayName": "图像显示",
"expressionRevealImage.renderer.revealImage.helpDescription": "显示一定百分比的图像,以制作定制的仪表样式图表",
"xpack.canvas.renderer.table.displayName": "数据表",
"xpack.canvas.renderer.table.helpDescription": "将表格数据呈现为 {HTML}",
"xpack.canvas.renderer.text.displayName": "纯文本",
@ -7026,8 +7028,6 @@
"expressionRevealImage.functions.revealImage.args.originHelpText": "要开始图像填充的位置。例如 {list} 或 {end}。",
"expressionRevealImage.functions.revealImage.invalidPercentErrorMessage": "无效值:“{percent}”。百分比必须介于 0 和 1 之间",
"expressionRevealImage.functions.revealImageHelpText": "配置图像显示元素。",
"expressionRevealImage.renderer.revealImage.displayName": "图像显示",
"expressionRevealImage.renderer.revealImage.helpDescription": "显示一定百分比的图像,以制作定制的仪表样式图表",
"xpack.cases.connectors.cases.externalIncidentAdded": " (由 {user} 于 {date}添加) ",
"xpack.cases.connectors.cases.externalIncidentCreated": " (由 {user} 于 {date}创建) ",
"xpack.cases.connectors.cases.externalIncidentDefault": " (由 {user} 于 {date}创建) ",