Iconpicker and Bugfixes

This commit is contained in:
Sergej Schwabauer 2022-03-04 11:50:41 +01:00
parent f6ec1e1639
commit b62e1a281e
13 changed files with 261 additions and 69 deletions

View File

@ -21,3 +21,5 @@ npm install @pnp/spfx-property-controls --save --save-exact
npm run serve
gulp clean; gulp build; gulp bundle --ship; gulp package-solution --ship;
npm run serve
exit
npm run serve

View File

@ -1,6 +1,6 @@
import * as React from 'react';
// import styles from './Autocomplete.module.scss';
import { TextField, ITextFieldProps, Callout, DirectionalHint, ITextField } from 'office-ui-fabric-react';
import styles from './Autocomplete.module.scss';
import { TextField, ITextFieldProps, Callout, ICalloutProps, DirectionalHint, ITextField, TextFieldBase } from 'office-ui-fabric-react';
import { isNullOrEmpty, cssClasses, getDeepOrDefault, isFunction } from '@spfxappdev/utility';
@ -9,6 +9,9 @@ export interface IAutocompleteProps extends Omit<ITextFieldProps, "componentRef"
minValueLength?: number;
onLoadSuggestions?(newValue: string): void;
onRenderSuggestions?(): JSX.Element;
textFieldRef?(fluentUITextField: ITextField, autocompleteComponent: Autocomplete, htmlInput?: HTMLInputElement);
onUpdated?(newValue: string);
calloutProps?: Omit<ICalloutProps, "hidden" | "target" | "preventDismissOnScroll" | "directionalHint" | "directionalHintFixed" | "isBeakVisible">;
}
interface IAutocompleteState {
@ -25,7 +28,10 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
public static defaultProps: IAutocompleteProps = {
showSuggestionsOnFocus: false,
minValueLength: 3
minValueLength: 3,
calloutProps: {
gapSpace: 0
}
};
private textFieldReference: ITextField = null;
@ -36,14 +42,21 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
private lastValue: string = "";
private onUpdateValueText: string = "";
public render(): React.ReactElement<IAutocompleteProps> {
return (<>
<TextField {...this.props}
className={cssClasses("styles.autocomplete", this.props.className)}
autoComplete={"off"}
className={cssClasses(styles.autocomplete, this.props.className)}
componentRef={(input: ITextField) => {
this.textFieldReference = input;
this.textFieldDomElement = getDeepOrDefault<HTMLInputElement>(input, "_textElement.current", null);
if(isFunction(this.props.textFieldRef)) {
this.props.textFieldRef(input, this, this.textFieldDomElement);
}
}}
onFocus={(ev: any) => {
@ -64,12 +77,7 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
}
}}
onChange={(ev: any, newValue: string) => {
this.onValueChanged(newValue);
if(isFunction(this.props.onChange)) {
this.props.onChange(ev, newValue);
}
this.onValueChanged(ev, newValue);
}}
defaultValue={this.state.currentValue}
/>
@ -78,17 +86,45 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
</>);
}
public updateValue(newValue: string): void {
this.onUpdateValueText = newValue;
this.setState({
currentValue: newValue
}, () => {
(this.textFieldReference as TextFieldBase).setState({
uncontrolledValue: this.onUpdateValueText
});
if(isFunction(this.props.onUpdated)) {
this.props.onUpdated(newValue);
}
});
}
private renderSuggesstionsFlyout(): JSX.Element {
let minWidth: number = getDeepOrDefault<number>(this.props, "calloutProps.calloutMinWidth", -1);
if(minWidth <= 0) {
minWidth = getDeepOrDefault<number>(this, "textFieldDomElement.clientWidth", -1);
}
if(minWidth > 0) {
this.props.calloutProps.calloutMinWidth = minWidth;
}
return (<Callout
{...this.props.calloutProps}
hidden={!this.state.isFlyoutVisible}
className={"styles.bagHeaderMegamenu"}
directionalHintFixed={true}
gapSpace={0}
// calloutWidth={window.outerWidth}
isBeakVisible={false}
target={this.textFieldDomElement}
onDismiss={() => {
onDismiss={(ev?: any) => {
this.hideSuggesstionsFlyout();
if(isFunction(this.props.calloutProps.onDismiss)) {
this.props.calloutProps.onDismiss(ev);
}
}}
preventDismissOnScroll={true}
directionalHint={DirectionalHint.bottomCenter}>
@ -97,7 +133,7 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
);
}
private onValueChanged(newValue: string): void {
private onValueChanged(ev: any, newValue: string): void {
this.userIsTyping = true;
this.state.currentValue = newValue;
@ -106,11 +142,17 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
});
this.handleSuggestionListVisibility();
if(isFunction(this.props.onChange)) {
this.props.onChange(ev, newValue);
}
}
private onTextFieldBlur(): void {
this.userIsTyping = false;
window.setTimeout(() => {
this.hideSuggesstionsFlyout();
}, 150);
}
private handleSuggestionListVisibility(): void {

View File

@ -1,4 +1,36 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.iconpicker {
display: block;
}
.suggesstion {
display: block;
&-item {
display: flex;
padding: 5px 20px;
border-bottom: solid 1px $ms-color-themeLighter;
border-top: solid 1px $ms-color-themeLighter;
align-items: center;
cursor: pointer;
&:first-child {
border-top-width: 0;
}
&:last-child {
border-bottom-width: 0;
}
i {
padding-right: 20px;
font-size: 2em;
}
span {
font-size: 1em;
}
}
}

View File

@ -1,26 +1,25 @@
import * as React from 'react';
import styles from './IconPicker.module.scss';
import { ITextFieldProps, Icon } from 'office-ui-fabric-react';
import { Icon, ITextField } from 'office-ui-fabric-react';
import { allIcons } from './availableIcons';
import { isNullOrEmpty, cssClasses } from '@spfxappdev/utility';
import { isNullOrEmpty, cssClasses, isFunction } from '@spfxappdev/utility';
import { Autocomplete, IAutocompleteProps } from '@src/components/autocomplete/Autocomplete';
export interface IIconPickerProps extends Omit<ITextFieldProps, "componentRef">, IAutocompleteProps {
export interface IIconPickerProps extends Omit<IAutocompleteProps, "onUpdated" | "onChange"> {
enableDialogPicker?: boolean;
dialogPickerIconName?: string;
onIconChanged?(iconName: string): void;
}
interface IIconPickerState {
currentValue: string;
isFlyoutVisible: boolean;
}
export class IconPicker extends React.Component<IIconPickerProps, IIconPickerState> {
public state: IIconPickerState = {
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue,
isFlyoutVisible: false,
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue
};
public static defaultProps: IIconPickerProps = {
@ -30,17 +29,41 @@ export class IconPicker extends React.Component<IIconPickerProps, IIconPickerSta
minValueLength: 0
};
private inputValueOnClick: string = "";
private textFieldReference: ITextField = null;
private textFieldDomElement: HTMLInputElement = null;
private autocompleteRef: Autocomplete = null;
public render(): React.ReactElement<IIconPickerProps> {
return (<>
<Autocomplete {...this.props}
textFieldRef={(fluentUITextField: ITextField, autocompleteComponent: Autocomplete, htmlInput: HTMLInputElement) => {
this.textFieldReference = fluentUITextField;
this.textFieldDomElement = htmlInput;
this.autocompleteRef = autocompleteComponent;
if(isFunction(this.props.textFieldRef)) {
this.props.textFieldRef(fluentUITextField, autocompleteComponent, this.textFieldDomElement);
}
}}
onChange={(ev: any, name: string) => {
if(isFunction(this.props.onIconChanged)) {
this.props.onIconChanged(name);
}
}}
onUpdated={(name: string) => {
if(isFunction(this.props.onIconChanged)) {
this.props.onIconChanged(name);
}
}}
className={cssClasses(styles.iconpicker)}
defaultValue={this.state.currentValue}
onLoadSuggestions={(newValue: string) => {
this.state.currentValue = newValue;
this.setState({
currentValue: newValue,
isFlyoutVisible: true
currentValue: newValue
});
}}
onRenderSuggestions={() => {
@ -56,13 +79,26 @@ export class IconPicker extends React.Component<IIconPickerProps, IIconPickerSta
private renderSuggesstionsFlyout(): JSX.Element {
return (<>
<ul>
return (
<div className={styles["suggesstion"]}>
{allIcons.Where(icon => icon.StartsWith(this.state.currentValue)).map((iconName: string): JSX.Element => {
return (<li key={`Icon_${iconName}`}><Icon iconName={iconName} /></li>);
return (<div
key={`Icon_${iconName}`}
onClick={() => {
this.inputValueOnClick = iconName;
this.setState({
currentValue: iconName
});
this.autocompleteRef.updateValue(iconName);
}}
className={styles["suggesstion-item"]}>
<Icon iconName={iconName} />
<span>{iconName}</span>
</div>);
})}
</ul>
</>
</div>
);
}
}

View File

@ -34,7 +34,8 @@
"markerCategories": [],
"center": [51.505, -0.09],
"startZoom": 13,
"maxZoom": 50,
"maxZoom": 30,
"minZoom": 1,
"dragging": true,
"height": 400,
"scrollWheelZoom": true,

View File

@ -6,9 +6,7 @@ import {
PropertyPaneTextField,
PropertyPaneToggle,
PropertyPaneSlider,
PropertyPaneButton,
PropertyPaneLabel
PropertyPaneButton
} from '@microsoft/sp-property-pane';
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
@ -19,6 +17,7 @@ import Map from './components/Map';
import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps';
import ManageMarkerCategoriesDialog, { IManageMarkerCategoriesDialogProps } from './components/ManageMarkerCategoriesDialog';
import { isNullOrEmpty } from '@spfxappdev/utility';
import { Spinner, ISpinnerProps } from '@microsoft/office-ui-fabric-react-bundle';
export interface IMapPlugins {
searchBox: boolean;
@ -34,6 +33,7 @@ export interface IMapWebPartProps {
center: [number, number];
startZoom: number;
maxZoom: number;
minZoom: number;
height: number;
scrollWheelZoom: boolean;
dragging: boolean;
@ -49,11 +49,10 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
private _isDarkTheme: boolean = false;
protected onInit(): Promise<void> {
return super.onInit();
return super.onInit();
}
public render(): void {
const element: React.ReactElement<IMapProps> = React.createElement(
Map,
{
@ -61,6 +60,8 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
markerCategories: this.properties.markerCategories||[],
isEditMode: this.displayMode == DisplayMode.Edit,
zoom: this.properties.startZoom,
minZoom: this.properties.minZoom,
maxZoom: this.properties.maxZoom,
center: this.properties.center,
title: this.properties.title,
height: this.properties.height,
@ -91,9 +92,37 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
ReactDom.render(element, this.domElement);
}
// protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
// super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue)
// console.log()
protected onDisplayModeChanged(oldDisplayMode: DisplayMode): void {
this.reload();
}
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
const reloadIfOneOfProps = ["height", "tileLayerUrl", "minZoom", "maxZoom", "tileLayerAttribution", "plugins.zoomControl"]
if(reloadIfOneOfProps.Contains(p => p.Equals(propertyPath))) {
this.reload();
}
}
private reload(): void {
setTimeout(() => {
const spinner: React.ReactElement<ISpinnerProps> = React.createElement(Spinner, {
});
ReactDom.render(spinner, this.domElement);
setTimeout(() => { this.render(); }, 300);
}, 500);
}
// protected get disableReactivePropertyChanges(): boolean {
// return true;
// }
private onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]): void {
@ -131,12 +160,15 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
{
groupName: strings.WebPartPropertyGroupMapSettings,
groupFields: [
// PropertyPaneToggle('plugins.searchBox', {
// label: "searchBox"
// PropertyPaneWebPartInformation({
// description: `<div class='wp-settings-info'>${strings.WebPartPropertySettingsInfoLabel}</div>`,
// key: 'Info_For_3f860b48-1dc3-496d-bd28-b145672289cc'
// }),
PropertyPaneWebPartInformation({
description: `<div class='wp-settings-info'>${strings.WebPartPropertySettingsInfoLabel}</div>`,
key: 'Info_For_3f860b48-1dc3-496d-bd28-b145672289cc'
PropertyPaneSlider('minZoom', {
label: strings.WebPartPropertyMinZoomLabel,
max: 30,
min: 0,
step: 1
}),
PropertyPaneSlider('maxZoom', {
label: strings.WebPartPropertyMaxZoomLabel,
@ -150,12 +182,6 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
max: 1200,
step: 50
}),
PropertyPaneTextField('tileLayerUrl', {
label: strings.WebPartPropertyTileLayerUrlLabel
}),
PropertyPaneTextField('tileLayerAttribution', {
label: strings.WebPartPropertyTileLayerAttributionLabel
}),
PropertyPaneToggle('scrollWheelZoom', {
label: strings.WebPartPropertyScrollWheelZoomLabel,
}),
@ -168,10 +194,29 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
]
},
{
isCollapsed: true,
groupName: strings.WebPartPropertyGroupTileLayerSettings,
groupFields: [
PropertyPaneWebPartInformation({
description: `<div class='wp-settings-info'>${strings.WebPartPropertyTileLayerUrlInformationLabel}</div>`,
key: 'Tile_For_3f860b48-1dc3-496d-bd28-b145672289cc'
}),
PropertyPaneTextField('tileLayerUrl', {
label: strings.WebPartPropertyTileLayerUrlLabel
}),
PropertyPaneTextField('tileLayerAttribution', {
label: strings.WebPartPropertyTileLayerAttributionLabel
}),
]
},
{
isCollapsed: true,
groupName: strings.WebPartPropertyGroupPlugins,
groupFields: [
// PropertyPaneToggle('plugins.searchBox', {
// label: "searchBox"
// }),
PropertyPaneToggle('plugins.markercluster', {
label: strings.WebPartPropertyPluginMarkerClusterLabel,
}),
@ -216,7 +261,7 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
groupFields: [
PropertyPaneWebPartInformation({
description: `<h3>Author</h3>
<a href='https://spfx-app.dev/'>SPFx-App.dev</a>
<a href='https://spfx-app.dev/' data-interception="off" target='_blank'>SPFx-App.dev</a>
<h3>Version</h3>
${this.context.manifest.version}
<h3>Web Part Instance id</h3>

View File

@ -13,6 +13,7 @@ import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import ManageMarkerCategoriesDialog from './ManageMarkerCategoriesDialog';
import { MarkerIcon } from './MarkerIcon';
import * as strings from 'MapWebPartStrings';
import { IconPicker } from '@src/components/iconPicker/IconPicker';
export interface IAddOrEditPanelProps {
markerItem: IMarker;
@ -192,13 +193,26 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
}}
/>
<TextField label={strings.LabelIcon} description={strings.LabelLeaveEmpty} defaultValue={this.state.markerItem.iconProperties.iconName} onChange={(ev: any, iconName: string) => {
{/* <TextField label={strings.LabelIcon} description={strings.LabelLeaveEmpty} defaultValue={this.state.markerItem.iconProperties.iconName} onChange={(ev: any, iconName: string) => {
this.state.markerItem.iconProperties.iconName = iconName;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}} />
}} /> */}
<IconPicker
label={strings.LabelIcon}
description={strings.LabelLeaveEmpty}
defaultValue={this.state.markerItem.iconProperties.iconName}
onIconChanged={(iconName: string) => {
this.state.markerItem.iconProperties.iconName = iconName;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}}
/>
<InlineColorPicker
label={strings.LabelIconColor}

View File

@ -50,6 +50,7 @@ export interface IMapProps {
zoom?: number;
center?: [number, number];
maxZoom?: number;
minZoom?: number;
title?: string;
height: number;
dragging: boolean;

View File

@ -166,7 +166,7 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
<div className='spfxappdev-grid-col spfxappdev-sm3'>
<IconPicker
defaultValue={categoryItem.iconProperties.iconName}
onChange={(ev: any, name: string) => {
onIconChanged={(name: string) => {
this.state.markerCategories[index].iconProperties.iconName = name;
this.validateForm();
}}

View File

@ -20,6 +20,11 @@
}
}
.display-mode .marker-type-none {
cursor: default !important;
}
.manage-categories-label {
font-size: 10px;
color: #1190F4;
@ -143,7 +148,7 @@
border-bottom: solid 1px #aeaeae;
&:nth-child(odd) {
background: $ms-color-themeLight;
background: $ms-color-themeLighter;
}
}
}

View File

@ -8,7 +8,7 @@ import "leaflet/dist/leaflet.css";
import "react-leaflet-markercluster/dist/styles.min.css";
import * as L from 'leaflet';
import { ContextualMenu, IContextualMenuItem, Panel, Dialog, IPanelProps, DefaultButton, PanelType, DialogType, DialogContent, Label, Separator, PrimaryButton } from 'office-ui-fabric-react';
import { isset, isNullOrEmpty, getDeepOrDefault } from '@spfxappdev/utility';
import { isset, isNullOrEmpty, getDeepOrDefault, cssClasses } from '@spfxappdev/utility';
import '@spfxappdev/utility/lib/extensions/StringExtensions';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import { DisplayMode } from '@microsoft/sp-core-library';
@ -86,7 +86,8 @@ export default class Map extends React.Component<IMapProps, IMapState> {
public render(): React.ReactElement<IMapProps> {
this.allLeafletMarker = {};
const isZoomControlEnabled: boolean = this.props.isEditMode ? true : getDeepOrDefault<boolean>(this.props, "plugins.zoomControl", true);
// const isZoomControlEnabled: boolean = this.props.isEditMode ? true : getDeepOrDefault<boolean>(this.props, "plugins.zoomControl", true);
const isZoomControlEnabled: boolean = getDeepOrDefault<boolean>(this.props, "plugins.zoomControl", true);
const isScrollWheelZoomEnabled: boolean = this.props.isEditMode ? true : getDeepOrDefault<boolean>(this.props, "scrollWheelZoom", true);
const isDraggingEnabled: boolean = this.props.isEditMode ? true : getDeepOrDefault<boolean>(this.props, "dragging", true);
//
@ -99,15 +100,16 @@ export default class Map extends React.Component<IMapProps, IMapState> {
}
<MapContainer
className={this.props.isEditMode ? "edit-mode" : "display-mode"}
zoomControl={isZoomControlEnabled}
center={this.props.center}
zoom={this.props.zoom}
maxZoom={this.props.maxZoom}
minZoom={this.props.minZoom}
scrollWheelZoom={isScrollWheelZoomEnabled}
touchZoom={isScrollWheelZoomEnabled}
doubleClickZoom={isScrollWheelZoomEnabled}
dragging={isDraggingEnabled}
whenCreated={(map: L.Map) => {
map.on("contextmenu", (ev: L.LeafletEvent) => {
@ -125,6 +127,10 @@ export default class Map extends React.Component<IMapProps, IMapState> {
});
});
map.on("zoom", (ev: any) => {
console.log("SSC", this.props.maxZoom, ev);
})
this.map = map;
}
}
@ -502,12 +508,14 @@ export default class Map extends React.Component<IMapProps, IMapState> {
shadowSize: null,
shadowAnchor: null,
iconSize: new L.Point(27, 36),
className: 'leaflet-div-icon'
className: cssClasses('leaflet-div-icon', `marker-type-${marker.type.toLowerCase()}`)
});
markerIcon.createIcon = (oldIcon: HTMLElement) => {
const wrapper = document.createElement("div");
wrapper.classList.add("leaflet-marker-icon");
wrapper.classList.add(`marker-type-${marker.type.toLowerCase()}`);
wrapper.dataset.markerid = marker.id;
wrapper.style.marginLeft = (markerIcon.options.iconAnchor[0] * -1) + "px";

View File

@ -1,6 +1,7 @@
define([], function() {
return {
"WebPartPropertyGroupMapSettings": "General settings",
"WebPartPropertyGroupTileLayerSettings": "Tile Layer",
"WebPartPropertyGroupPlugins": "Plugins/Controls",
"WebPartPropertyGroupCategories": "Categories",
"WebPartPropertyGroupAbout": "About",
@ -9,13 +10,15 @@ define([], function() {
"WebPartPropertyPluginLegendLabel": "Show legend",
"WebPartPropertyButtonManageCategories": "Manage categories",
"WebPartPropertyPluginZoomControlLabel": "Show zoom control",
"WebPartPropertyScrollWheelZoomLabel": "Enable zoom on mouse wheel/touch",
"WebPartPropertyMapDraggingLabel": "Enable dragging in map",
"WebPartPropertyScrollWheelZoomLabel": "Enable zoom on mouse wheel/touch (only in display mode)",
"WebPartPropertyMapDraggingLabel": "Enable dragging in map (only in display mode)",
"WebPartPropertyShowPopUpLabel": "Show tooltip when hovering the marker",
"WebPartPropertySettingsInfoLabel": "Most of these settings take effect only after a page refresh and only in display mode",
"WebPartPropertyMaxZoomLabel": "Maximum zoom level (depends on Tile layer)",
"WebPartPropertyHeightLabel": "Map height",
"WebPartPropertyTileLayerUrlLabel": "Tile layer URL (Examples can be found here: https://leaflet-extras.github.io/leaflet-providers/preview/)",
"WebPartPropertyMinZoomLabel": "Minimum zoom level (zoom out)",
"WebPartPropertyMaxZoomLabel": "Maximum zoom level (zoom in, depends on Tile layer)",
"WebPartPropertyHeightLabel": "Map height (in px)",
"WebPartPropertyTileLayerUrlInformationLabel": "In this section you can change the tile layer and attribution. You can find more tile layers e.g. <a href='https://leaflet-extras.github.io/leaflet-providers/preview/' data-interception='off' target='_blank'>here</a>",
"WebPartPropertyTileLayerUrlLabel": "Tile layer URL",
"WebPartPropertyTileLayerAttributionLabel": "Tile layer attribution",
"ContextMenuAddNewMarkerLabel": "Add a new marker here",
"ContextMenuSetStartPositionLabel": "Make this view as start position",

View File

@ -1,5 +1,6 @@
declare interface IMapWebPartStrings {
WebPartPropertyGroupMapSettings: string;
WebPartPropertyGroupTileLayerSettings: string;
WebPartPropertyGroupPlugins: string;
WebPartPropertyGroupCategories: string;
WebPartPropertyGroupAbout: string;
@ -12,8 +13,10 @@ declare interface IMapWebPartStrings {
WebPartPropertyMapDraggingLabel: string;
WebPartPropertyShowPopUpLabel: string;
WebPartPropertySettingsInfoLabel: string;
WebPartPropertyMinZoomLabel: string;
WebPartPropertyMaxZoomLabel: string;
WebPartPropertyHeightLabel: string;
WebPartPropertyTileLayerUrlInformationLabel: string;
WebPartPropertyTileLayerUrlLabel: string;
WebPartPropertyTileLayerAttributionLabel: string;
ContextMenuAddNewMarkerLabel: string;