Improve VisParam typings (#42695)

* Begin typing VisParams in old editor

* Forgotten save

* Continue updating types

* Adjust tilemap typings

* Use proper generic name

* Reuse vis param types in expression fn

* Address review

* Optimize types

* Fix typing
This commit is contained in:
Tim Roes 2019-08-07 15:38:42 +02:00 committed by GitHub
parent 2f50ae8f68
commit 7bb48e253e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 209 additions and 96 deletions

View file

@ -24,7 +24,16 @@ import { VisOptionsProps } from 'ui/vis/editors/default';
import { SwitchOption } from './switch'; import { SwitchOption } from './switch';
import { SelectOption } from './select'; import { SelectOption } from './select';
function BasicOptions({ stateParams, setValue, vis }: VisOptionsProps) { export interface BasicOptionsParams {
addTooltip: boolean;
legendPosition: string;
}
function BasicOptions<VisParams extends BasicOptionsParams>({
stateParams,
setValue,
vis,
}: VisOptionsProps<VisParams>) {
return ( return (
<> <>
<SelectOption <SelectOption

View file

@ -19,19 +19,26 @@
import React from 'react'; import React from 'react';
import { EuiFormRow, EuiRange } from '@elastic/eui'; import { EuiFormRow, EuiRange } from '@elastic/eui';
import { VisOptionsSetValue } from 'ui/vis/editors/default';
interface RangeOptionProps { interface RangeOptionProps<ParamName extends string> {
label: string; label: string;
max: number; max: number;
min: number; min: number;
paramName: string; paramName: ParamName;
step?: number; step?: number;
value: string | number; value: '' | number;
setValue: VisOptionsSetValue; setValue: (paramName: ParamName, value: number) => void;
} }
function RangeOption({ label, max, min, step, paramName, value, setValue }: RangeOptionProps) { function RangeOption<ParamName extends string>({
label,
max,
min,
step,
paramName,
value,
setValue,
}: RangeOptionProps<ParamName>) {
return ( return (
<EuiFormRow label={label} fullWidth={true} compressed> <EuiFormRow label={label} fullWidth={true} compressed>
<EuiRange <EuiRange
@ -41,7 +48,7 @@ function RangeOption({ label, max, min, step, paramName, value, setValue }: Rang
min={min} min={min}
step={step} step={step}
value={value} value={value}
onChange={ev => setValue(paramName, ev.target.value)} onChange={ev => setValue(paramName, ev.target.valueAsNumber)}
/> />
</EuiFormRow> </EuiFormRow>
); );

View file

@ -19,23 +19,28 @@
import React from 'react'; import React from 'react';
import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { EuiFormRow, EuiSelect } from '@elastic/eui';
import { VisOptionsSetValue } from 'ui/vis/editors/default';
interface SelectOptionProps { interface SelectOptionProps<ParamName extends string, ValidParamValues extends string> {
label: string; label: string;
options: Array<{ value: string; text: string }>; options: Array<{ value: ValidParamValues; text: string }>;
paramName: string; paramName: ParamName;
value: string; value?: ValidParamValues;
setValue: VisOptionsSetValue; setValue: (paramName: ParamName, value: ValidParamValues) => void;
} }
function SelectOption({ label, options, paramName, value, setValue }: SelectOptionProps) { function SelectOption<ParamName extends string, ValidParamValues extends string>({
label,
options,
paramName,
value,
setValue,
}: SelectOptionProps<ParamName, ValidParamValues>) {
return ( return (
<EuiFormRow label={label} fullWidth={true} compressed> <EuiFormRow label={label} fullWidth={true} compressed>
<EuiSelect <EuiSelect
options={options} options={options}
value={value} value={value}
onChange={ev => setValue(paramName, ev.target.value)} onChange={ev => setValue(paramName, ev.target.value as ValidParamValues)}
fullWidth={true} fullWidth={true}
/> />
</EuiFormRow> </EuiFormRow>

View file

@ -20,19 +20,18 @@
import React from 'react'; import React from 'react';
import { EuiSwitch, EuiToolTip } from '@elastic/eui'; import { EuiSwitch, EuiToolTip } from '@elastic/eui';
import { VisOptionsSetValue } from 'ui/vis/editors/default';
interface SwitchOptionProps { interface SwitchOptionProps<ParamName extends string> {
dataTestSubj?: string; dataTestSubj?: string;
label?: string; label?: string;
tooltip?: string; tooltip?: string;
disabled?: boolean; disabled?: boolean;
value?: boolean; value?: boolean;
paramName: string; paramName: ParamName;
setValue: VisOptionsSetValue; setValue: (paramName: ParamName, value: boolean) => void;
} }
function SwitchOption({ function SwitchOption<ParamName extends string>({
dataTestSubj, dataTestSubj,
tooltip, tooltip,
label, label,
@ -40,7 +39,7 @@ function SwitchOption({
paramName, paramName,
value = false, value = false,
setValue, setValue,
}: SwitchOptionProps) { }: SwitchOptionProps<ParamName>) {
return ( return (
<div className="visEditorSidebar__switchOptionFormRow"> <div className="visEditorSidebar__switchOptionFormRow">
<EuiToolTip content={tooltip} delay="long" position="right"> <EuiToolTip content={tooltip} delay="long" position="right">

View file

@ -19,23 +19,22 @@
import React from 'react'; import React from 'react';
import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { EuiFormRow, EuiFieldText } from '@elastic/eui';
import { VisOptionsSetValue } from 'ui/vis/editors/default';
interface TextInputOptionProps { interface TextInputOptionProps<ParamName extends string> {
helpText?: React.ReactNode; helpText?: React.ReactNode;
label?: React.ReactNode; label?: React.ReactNode;
paramName: string; paramName: ParamName;
value?: string; value?: string;
setValue: VisOptionsSetValue; setValue: (paramName: ParamName, value: string) => void;
} }
function TextInputOption({ function TextInputOption<ParamName extends string>({
helpText, helpText,
label, label,
paramName, paramName,
value = '', value = '',
setValue, setValue,
}: TextInputOptionProps) { }: TextInputOptionProps<ParamName>) {
return ( return (
<EuiFormRow helpText={helpText} label={label} fullWidth compressed> <EuiFormRow helpText={helpText} label={label} fullWidth compressed>
<EuiFieldText fullWidth value={value} onChange={ev => setValue(paramName, ev.target.value)} /> <EuiFieldText fullWidth value={value} onChange={ev => setValue(paramName, ev.target.value)} />

View file

@ -20,11 +20,10 @@
import React, { ChangeEvent } from 'react'; import React, { ChangeEvent } from 'react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; import { EuiFormRow, EuiFieldNumber } from '@elastic/eui';
import { VisOptionsSetValue } from 'ui/vis/editors/default';
interface TruncateLabelsOptionProps { interface TruncateLabelsOptionProps {
value: number | null; value: number | null;
setValue: VisOptionsSetValue; setValue: (paramName: 'truncate', value: null | number) => void;
} }
function TruncateLabelsOption({ value, setValue }: TruncateLabelsOptionProps) { function TruncateLabelsOption({ value, setValue }: TruncateLabelsOptionProps) {

View file

@ -21,15 +21,18 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps, VisOptionsSetValue } from 'ui/vis/editors/default'; import { VisOptionsProps } from 'ui/vis/editors/default';
import { BasicOptions } from '../controls/basic_options'; import { BasicOptions } from '../controls/basic_options';
import { SwitchOption } from '../controls/switch'; import { SwitchOption } from '../controls/switch';
import { TruncateLabelsOption } from '../controls/truncate_labels'; import { TruncateLabelsOption } from '../controls/truncate_labels';
import { PieVisParams } from '../pie';
function PieOptions(props: VisOptionsProps) { function PieOptions(props: VisOptionsProps<PieVisParams>) {
const { stateParams, setValue } = props; const { stateParams, setValue } = props;
const setLabels: VisOptionsSetValue = (paramName, value) => const setLabels = <T extends keyof PieVisParams['labels']>(
setValue('labels', { ...stateParams.labels, [paramName]: value }); paramName: T,
value: PieVisParams['labels'][T]
) => setValue('labels', { ...stateParams.labels, [paramName]: value });
return ( return (
<> <>

View file

@ -17,19 +17,15 @@
* under the License. * under the License.
*/ */
import React from 'react'; import { CommonVislibParams } from './types';
import { VisOptionsProps } from 'ui/vis/editors/default';
import { ServiceSettings } from 'ui/vis/map/service_settings';
export interface InjectedDependencies { export interface PieVisParams extends CommonVislibParams {
serviceSettings: ServiceSettings; addLegend: boolean;
isDonut: boolean;
labels: {
show: boolean;
values: boolean;
last_level: boolean;
truncate: number | null;
};
} }
export type ExtendedVisOptionsProps = VisOptionsProps & InjectedDependencies;
const withInjectedDependencies = (
Component: React.ComponentType<ExtendedVisOptionsProps>,
dependencies: InjectedDependencies
) => (props: VisOptionsProps) => <Component {...props} {...dependencies} />;
export { withInjectedDependencies };

View file

@ -0,0 +1,23 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export interface CommonVislibParams {
addTooltip: boolean;
legendPosition: 'right' | 'left' | 'top' | 'bottom';
}

View file

@ -21,14 +21,20 @@ import React, { useEffect } from 'react';
import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { EuiPanel, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { ExtendedVisOptionsProps } from '../../../kbn_vislib_vis_types/public/utils/with_injected_dependencies'; import { ServiceSettings } from 'ui/vis/map/service_settings';
import { VisOptionsProps } from 'ui/vis/editors/default';
import { SelectOption } from '../../../kbn_vislib_vis_types/public/controls/select'; import { SelectOption } from '../../../kbn_vislib_vis_types/public/controls/select';
import { RangeOption } from '../../../kbn_vislib_vis_types/public/controls/range'; import { RangeOption } from '../../../kbn_vislib_vis_types/public/controls/range';
import { BasicOptions } from '../../../kbn_vislib_vis_types/public/controls/basic_options'; import { BasicOptions } from '../../../kbn_vislib_vis_types/public/controls/basic_options';
import { SwitchOption } from '../../../kbn_vislib_vis_types/public/controls/switch'; import { SwitchOption } from '../../../kbn_vislib_vis_types/public/controls/switch';
import { WmsOptions } from './wms_options'; import { WmsOptions } from './wms_options';
import { TileMapVisParams } from '../types';
function TileMapOptions(props: ExtendedVisOptionsProps) { export type TileMapOptionsProps = { serviceSettings: ServiceSettings } & VisOptionsProps<
TileMapVisParams
>;
function TileMapOptions(props: TileMapOptionsProps) {
const { stateParams, setValue, vis } = props; const { stateParams, setValue, vis } = props;
useEffect(() => { useEffect(() => {

View file

@ -21,26 +21,15 @@ import React from 'react';
import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsSetValue } from 'ui/vis/editors/default';
import { TmsLayer } from 'ui/vis/map/service_settings';
import { TextInputOption } from '../../../kbn_vislib_vis_types/public/controls/text_input'; import { TextInputOption } from '../../../kbn_vislib_vis_types/public/controls/text_input';
import { TileMapVisParams } from '../types';
interface WmsOptions {
enabled: boolean;
url: string;
selectedTmsLayer: TmsLayer;
options: {
attribution: string;
format: string;
layers: string;
styles: string;
version: string;
};
}
interface WmsInternalOptions { interface WmsInternalOptions {
wms: WmsOptions; wms: TileMapVisParams['wms'];
setValue: VisOptionsSetValue; setValue: <T extends keyof TileMapVisParams['wms']>(
paramName: T,
value: TileMapVisParams['wms'][T]
) => void;
} }
function WmsInternalOptions({ wms, setValue }: WmsInternalOptions) { function WmsInternalOptions({ wms, setValue }: WmsInternalOptions) {
@ -64,7 +53,10 @@ function WmsInternalOptions({ wms, setValue }: WmsInternalOptions) {
</EuiScreenReaderOnly> </EuiScreenReaderOnly>
); );
const setOptions: VisOptionsSetValue = (paramName, value) => const setOptions = <T extends keyof TileMapVisParams['wms']['options']>(
paramName: T,
value: TileMapVisParams['wms']['options'][T]
) =>
setValue('options', { setValue('options', {
...wms.options, ...wms.options,
[paramName]: value, [paramName]: value,

View file

@ -23,28 +23,39 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { toastNotifications } from 'ui/notify'; import { toastNotifications } from 'ui/notify';
import { VisOptionsSetValue } from 'ui/vis/editors/default';
import { TmsLayer } from 'ui/vis/map/service_settings'; import { TmsLayer } from 'ui/vis/map/service_settings';
import { ExtendedVisOptionsProps } from '../../../kbn_vislib_vis_types/public/utils/with_injected_dependencies';
import { SelectOption } from '../../../kbn_vislib_vis_types/public/controls/select'; import { SelectOption } from '../../../kbn_vislib_vis_types/public/controls/select';
import { SwitchOption } from '../../../kbn_vislib_vis_types/public/controls/switch'; import { SwitchOption } from '../../../kbn_vislib_vis_types/public/controls/switch';
import { WmsInternalOptions } from './wms_internal_options'; import { WmsInternalOptions } from './wms_internal_options';
import { TileMapOptionsProps } from './tile_map_options';
import { TileMapVisParams } from '../types';
const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id }); const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id, value: id });
function WmsOptions({ serviceSettings, stateParams, setValue, vis }: ExtendedVisOptionsProps) { function WmsOptions({ serviceSettings, stateParams, setValue, vis }: TileMapOptionsProps) {
const { wms } = stateParams; const { wms } = stateParams;
const { tmsLayers } = vis.type.editorConfig.collections; const { tmsLayers } = vis.type.editorConfig.collections;
const [tmsLayerOptions, setTmsLayersOptions] = useState( const [tmsLayerOptions, setTmsLayersOptions] = useState<Array<{ text: string; value: string }>>(
vis.type.editorConfig.collections.tmsLayers.map(mapLayerForOption) vis.type.editorConfig.collections.tmsLayers.map(mapLayerForOption)
); );
const [layers, setLayers] = useState<TmsLayer[]>([]);
const setWmsOption: VisOptionsSetValue = (paramName, value) => const setWmsOption = <T extends keyof TileMapVisParams['wms']>(
paramName: T,
value: TileMapVisParams['wms'][T]
) =>
setValue('wms', { setValue('wms', {
...wms, ...wms,
[paramName]: value, [paramName]: value,
}); });
const selectTmsLayer = (id: string) => {
const layer = layers.find(l => l.id === id);
if (layer) {
setWmsOption('selectedTmsLayer', layer);
}
};
useEffect(() => { useEffect(() => {
serviceSettings serviceSettings
.getTMSServices() .getTMSServices()
@ -54,6 +65,7 @@ function WmsOptions({ serviceSettings, stateParams, setValue, vis }: ExtendedVis
...services.filter(service => !tmsLayers.some(({ id }: TmsLayer) => service.id === id)), ...services.filter(service => !tmsLayers.some(({ id }: TmsLayer) => service.id === id)),
]; ];
setLayers(newBaseLayers);
setTmsLayersOptions(newBaseLayers.map(mapLayerForOption)); setTmsLayersOptions(newBaseLayers.map(mapLayerForOption));
if (!wms.selectedTmsLayer && newBaseLayers.length) { if (!wms.selectedTmsLayer && newBaseLayers.length) {
@ -97,7 +109,7 @@ function WmsOptions({ serviceSettings, stateParams, setValue, vis }: ExtendedVis
options={tmsLayerOptions} options={tmsLayerOptions}
paramName="selectedTmsLayer" paramName="selectedTmsLayer"
value={wms.selectedTmsLayer && wms.selectedTmsLayer.id} value={wms.selectedTmsLayer && wms.selectedTmsLayer.id}
setValue={setWmsOption} setValue={(param, value) => selectTmsLayer(value)}
/> />
</> </>
)} )}

View file

@ -17,6 +17,7 @@
* under the License. * under the License.
*/ */
import React from 'react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { supports } from 'ui/utils/supports'; import { supports } from 'ui/utils/supports';
@ -28,7 +29,6 @@ import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson';
import { createTileMapVisualization } from './tile_map_visualization'; import { createTileMapVisualization } from './tile_map_visualization';
import { visFactory } from '../../visualizations/public'; import { visFactory } from '../../visualizations/public';
import { TileMapOptions } from './components/tile_map_options'; import { TileMapOptions } from './components/tile_map_options';
import { withInjectedDependencies } from '../../kbn_vislib_vis_types/public/utils/with_injected_dependencies';
export function createTileMapTypeDefinition(dependencies) { export function createTileMapTypeDefinition(dependencies) {
const CoordinateMapsVisualization = createTileMapVisualization(dependencies); const CoordinateMapsVisualization = createTileMapVisualization(dependencies);
@ -113,7 +113,7 @@ export function createTileMapTypeDefinition(dependencies) {
], ],
tmsLayers: [], tmsLayers: [],
}, },
optionsTemplate: withInjectedDependencies(TileMapOptions, { serviceSettings }), optionsTemplate: (props) => <TileMapOptions {...props} serviceSettings={serviceSettings} />,
schemas: new Schemas([ schemas: new Schemas([
{ {
group: 'metrics', group: 'metrics',

View file

@ -0,0 +1,44 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export interface TileMapVisParams {
colorSchema: string;
mapType: 'Scaled Circle Markers' | 'Shaded Circle Markers' | 'Shaded geohash grid' | 'Heatmap';
isDesaturated: boolean;
addTooltip: boolean;
heatClusterSize: number;
legendPosition: 'bottomright' | 'bottomleft' | 'topright' | 'topleft';
mapZoom: number;
mapCenter: [number, number];
wms: {
selectedTmsLayer?: {
id: string;
};
enabled: boolean;
url?: string;
options: {
version?: string;
layers?: string;
format: string;
transparent: boolean;
attribution?: string;
styles?: string;
};
};
}

View file

@ -25,11 +25,12 @@ import { ValidatedDualRange } from 'ui/validated_range';
import { VisOptionsProps } from 'ui/vis/editors/default'; import { VisOptionsProps } from 'ui/vis/editors/default';
import { SelectOption } from '../../../kbn_vislib_vis_types/public/controls/select'; import { SelectOption } from '../../../kbn_vislib_vis_types/public/controls/select';
import { SwitchOption } from '../../../kbn_vislib_vis_types/public/controls/switch'; import { SwitchOption } from '../../../kbn_vislib_vis_types/public/controls/switch';
import { TagCloudVisParams } from '../types';
function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps) { function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps<TagCloudVisParams>) {
const handleFontSizeChange = ([minFontSize, maxFontSize]: string[]) => { const handleFontSizeChange = ([minFontSize, maxFontSize]: [string | number, string | number]) => {
setValue('minFontSize', minFontSize); setValue('minFontSize', Number(minFontSize));
setValue('maxFontSize', maxFontSize); setValue('maxFontSize', Number(maxFontSize));
}; };
const fontSizeRangeLabel = i18n.translate('visTypeTagCloud.visParams.fontSizeLabel', { const fontSizeRangeLabel = i18n.translate('visTypeTagCloud.visParams.fontSizeLabel', {
defaultMessage: 'Font size range in pixels', defaultMessage: 'Font size range in pixels',

View file

@ -20,27 +20,21 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { ExpressionFunction, KibanaDatatable, Render } from '../../interpreter/types'; import { ExpressionFunction, KibanaDatatable, Render } from '../../interpreter/types';
import { TagCloudVisParams } from './types';
const name = 'tagcloud'; const name = 'tagcloud';
type Context = KibanaDatatable; type Context = KibanaDatatable;
interface Arguments { interface Arguments extends TagCloudVisParams {
scale: string;
orientation: string;
minFontSize: number;
maxFontSize: number;
showLabel: boolean;
metric: any; // these aren't typed yet metric: any; // these aren't typed yet
bucket: any; // these aren't typed yet bucket: any; // these aren't typed yet
} }
type VisParams = Omit<Arguments, 'bucket'>;
interface RenderValue { interface RenderValue {
visType: typeof name; visType: typeof name;
visData: Context; visData: Context;
visConfig: VisParams; visConfig: Arguments;
params: any; params: any;
} }

View file

@ -0,0 +1,26 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export interface TagCloudVisParams {
scale: 'linear' | 'log' | 'square root';
orientation: 'single' | 'right angled' | 'multiple';
minFontSize: number;
maxFontSize: number;
showLabel: boolean;
}

View file

@ -17,12 +17,10 @@
* under the License. * under the License.
*/ */
import { Vis, VisParams } from 'ui/vis'; import { Vis } from 'ui/vis';
export type VisOptionsSetValue = (paramName: string, value: unknown) => void; export interface VisOptionsProps<VisParamType = unknown> {
stateParams: VisParamType;
export interface VisOptionsProps {
stateParams: VisParams;
vis: Vis; vis: Vis;
setValue: VisOptionsSetValue; setValue<T extends keyof VisParamType>(paramName: T, value: VisParamType[T]): void;
} }