Iconpicker and Bugfixes
This commit is contained in:
parent
f6ec1e1639
commit
b62e1a281e
|
@ -21,3 +21,5 @@ npm install @pnp/spfx-property-controls --save --save-exact
|
||||||
npm run serve
|
npm run serve
|
||||||
gulp clean; gulp build; gulp bundle --ship; gulp package-solution --ship;
|
gulp clean; gulp build; gulp bundle --ship; gulp package-solution --ship;
|
||||||
npm run serve
|
npm run serve
|
||||||
|
exit
|
||||||
|
npm run serve
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
// import styles from './Autocomplete.module.scss';
|
import styles from './Autocomplete.module.scss';
|
||||||
import { TextField, ITextFieldProps, Callout, DirectionalHint, ITextField } from 'office-ui-fabric-react';
|
import { TextField, ITextFieldProps, Callout, ICalloutProps, DirectionalHint, ITextField, TextFieldBase } from 'office-ui-fabric-react';
|
||||||
import { isNullOrEmpty, cssClasses, getDeepOrDefault, isFunction } from '@spfxappdev/utility';
|
import { isNullOrEmpty, cssClasses, getDeepOrDefault, isFunction } from '@spfxappdev/utility';
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ export interface IAutocompleteProps extends Omit<ITextFieldProps, "componentRef"
|
||||||
minValueLength?: number;
|
minValueLength?: number;
|
||||||
onLoadSuggestions?(newValue: string): void;
|
onLoadSuggestions?(newValue: string): void;
|
||||||
onRenderSuggestions?(): JSX.Element;
|
onRenderSuggestions?(): JSX.Element;
|
||||||
|
textFieldRef?(fluentUITextField: ITextField, autocompleteComponent: Autocomplete, htmlInput?: HTMLInputElement);
|
||||||
|
onUpdated?(newValue: string);
|
||||||
|
calloutProps?: Omit<ICalloutProps, "hidden" | "target" | "preventDismissOnScroll" | "directionalHint" | "directionalHintFixed" | "isBeakVisible">;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAutocompleteState {
|
interface IAutocompleteState {
|
||||||
|
@ -25,7 +28,10 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
|
||||||
|
|
||||||
public static defaultProps: IAutocompleteProps = {
|
public static defaultProps: IAutocompleteProps = {
|
||||||
showSuggestionsOnFocus: false,
|
showSuggestionsOnFocus: false,
|
||||||
minValueLength: 3
|
minValueLength: 3,
|
||||||
|
calloutProps: {
|
||||||
|
gapSpace: 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private textFieldReference: ITextField = null;
|
private textFieldReference: ITextField = null;
|
||||||
|
@ -36,14 +42,21 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
|
||||||
|
|
||||||
private lastValue: string = "";
|
private lastValue: string = "";
|
||||||
|
|
||||||
|
private onUpdateValueText: string = "";
|
||||||
|
|
||||||
public render(): React.ReactElement<IAutocompleteProps> {
|
public render(): React.ReactElement<IAutocompleteProps> {
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<TextField {...this.props}
|
<TextField {...this.props}
|
||||||
className={cssClasses("styles.autocomplete", this.props.className)}
|
autoComplete={"off"}
|
||||||
|
className={cssClasses(styles.autocomplete, this.props.className)}
|
||||||
componentRef={(input: ITextField) => {
|
componentRef={(input: ITextField) => {
|
||||||
this.textFieldReference = input;
|
this.textFieldReference = input;
|
||||||
this.textFieldDomElement = getDeepOrDefault<HTMLInputElement>(input, "_textElement.current", null);
|
this.textFieldDomElement = getDeepOrDefault<HTMLInputElement>(input, "_textElement.current", null);
|
||||||
|
|
||||||
|
if(isFunction(this.props.textFieldRef)) {
|
||||||
|
this.props.textFieldRef(input, this, this.textFieldDomElement);
|
||||||
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
onFocus={(ev: any) => {
|
onFocus={(ev: any) => {
|
||||||
|
@ -64,12 +77,7 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onChange={(ev: any, newValue: string) => {
|
onChange={(ev: any, newValue: string) => {
|
||||||
|
this.onValueChanged(ev, newValue);
|
||||||
this.onValueChanged(newValue);
|
|
||||||
|
|
||||||
if(isFunction(this.props.onChange)) {
|
|
||||||
this.props.onChange(ev, newValue);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
defaultValue={this.state.currentValue}
|
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 {
|
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
|
return (<Callout
|
||||||
|
{...this.props.calloutProps}
|
||||||
hidden={!this.state.isFlyoutVisible}
|
hidden={!this.state.isFlyoutVisible}
|
||||||
className={"styles.bagHeaderMegamenu"}
|
|
||||||
directionalHintFixed={true}
|
directionalHintFixed={true}
|
||||||
gapSpace={0}
|
|
||||||
// calloutWidth={window.outerWidth}
|
|
||||||
isBeakVisible={false}
|
isBeakVisible={false}
|
||||||
target={this.textFieldDomElement}
|
target={this.textFieldDomElement}
|
||||||
onDismiss={() => {
|
onDismiss={(ev?: any) => {
|
||||||
this.hideSuggesstionsFlyout();
|
this.hideSuggesstionsFlyout();
|
||||||
|
|
||||||
|
if(isFunction(this.props.calloutProps.onDismiss)) {
|
||||||
|
this.props.calloutProps.onDismiss(ev);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
preventDismissOnScroll={true}
|
preventDismissOnScroll={true}
|
||||||
directionalHint={DirectionalHint.bottomCenter}>
|
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.userIsTyping = true;
|
||||||
|
|
||||||
this.state.currentValue = newValue;
|
this.state.currentValue = newValue;
|
||||||
|
@ -106,11 +142,17 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
|
||||||
});
|
});
|
||||||
|
|
||||||
this.handleSuggestionListVisibility();
|
this.handleSuggestionListVisibility();
|
||||||
|
|
||||||
|
if(isFunction(this.props.onChange)) {
|
||||||
|
this.props.onChange(ev, newValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onTextFieldBlur(): void {
|
private onTextFieldBlur(): void {
|
||||||
this.userIsTyping = false;
|
this.userIsTyping = false;
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.hideSuggesstionsFlyout();
|
||||||
|
}, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleSuggestionListVisibility(): void {
|
private handleSuggestionListVisibility(): void {
|
||||||
|
|
|
@ -1,4 +1,36 @@
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
|
||||||
.iconpicker {
|
.iconpicker {
|
||||||
display: block;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +1,25 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styles from './IconPicker.module.scss';
|
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 { allIcons } from './availableIcons';
|
||||||
import { isNullOrEmpty, cssClasses } from '@spfxappdev/utility';
|
import { isNullOrEmpty, cssClasses, isFunction } from '@spfxappdev/utility';
|
||||||
import { Autocomplete, IAutocompleteProps } from '@src/components/autocomplete/Autocomplete';
|
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;
|
enableDialogPicker?: boolean;
|
||||||
dialogPickerIconName?: string;
|
dialogPickerIconName?: string;
|
||||||
|
onIconChanged?(iconName: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IIconPickerState {
|
interface IIconPickerState {
|
||||||
currentValue: string;
|
currentValue: string;
|
||||||
isFlyoutVisible: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IconPicker extends React.Component<IIconPickerProps, IIconPickerState> {
|
export class IconPicker extends React.Component<IIconPickerProps, IIconPickerState> {
|
||||||
|
|
||||||
public state: IIconPickerState = {
|
public state: IIconPickerState = {
|
||||||
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue,
|
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue
|
||||||
isFlyoutVisible: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static defaultProps: IIconPickerProps = {
|
public static defaultProps: IIconPickerProps = {
|
||||||
|
@ -30,17 +29,41 @@ export class IconPicker extends React.Component<IIconPickerProps, IIconPickerSta
|
||||||
minValueLength: 0
|
minValueLength: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private inputValueOnClick: string = "";
|
||||||
|
|
||||||
|
private textFieldReference: ITextField = null;
|
||||||
|
|
||||||
|
private textFieldDomElement: HTMLInputElement = null;
|
||||||
|
|
||||||
|
private autocompleteRef: Autocomplete = null;
|
||||||
|
|
||||||
public render(): React.ReactElement<IIconPickerProps> {
|
public render(): React.ReactElement<IIconPickerProps> {
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Autocomplete {...this.props}
|
<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)}
|
className={cssClasses(styles.iconpicker)}
|
||||||
defaultValue={this.state.currentValue}
|
defaultValue={this.state.currentValue}
|
||||||
onLoadSuggestions={(newValue: string) => {
|
onLoadSuggestions={(newValue: string) => {
|
||||||
this.state.currentValue = newValue;
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
currentValue: newValue,
|
currentValue: newValue
|
||||||
isFlyoutVisible: true
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onRenderSuggestions={() => {
|
onRenderSuggestions={() => {
|
||||||
|
@ -56,13 +79,26 @@ export class IconPicker extends React.Component<IIconPickerProps, IIconPickerSta
|
||||||
|
|
||||||
private renderSuggesstionsFlyout(): JSX.Element {
|
private renderSuggesstionsFlyout(): JSX.Element {
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
<ul>
|
<div className={styles["suggesstion"]}>
|
||||||
{allIcons.Where(icon => icon.StartsWith(this.state.currentValue)).map((iconName: string): JSX.Element => {
|
{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>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -34,7 +34,8 @@
|
||||||
"markerCategories": [],
|
"markerCategories": [],
|
||||||
"center": [51.505, -0.09],
|
"center": [51.505, -0.09],
|
||||||
"startZoom": 13,
|
"startZoom": 13,
|
||||||
"maxZoom": 50,
|
"maxZoom": 30,
|
||||||
|
"minZoom": 1,
|
||||||
"dragging": true,
|
"dragging": true,
|
||||||
"height": 400,
|
"height": 400,
|
||||||
"scrollWheelZoom": true,
|
"scrollWheelZoom": true,
|
||||||
|
|
|
@ -6,9 +6,7 @@ import {
|
||||||
PropertyPaneTextField,
|
PropertyPaneTextField,
|
||||||
PropertyPaneToggle,
|
PropertyPaneToggle,
|
||||||
PropertyPaneSlider,
|
PropertyPaneSlider,
|
||||||
PropertyPaneButton,
|
PropertyPaneButton
|
||||||
PropertyPaneLabel
|
|
||||||
|
|
||||||
} from '@microsoft/sp-property-pane';
|
} from '@microsoft/sp-property-pane';
|
||||||
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
|
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
|
||||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
@ -19,6 +17,7 @@ import Map from './components/Map';
|
||||||
import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps';
|
import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps';
|
||||||
import ManageMarkerCategoriesDialog, { IManageMarkerCategoriesDialogProps } from './components/ManageMarkerCategoriesDialog';
|
import ManageMarkerCategoriesDialog, { IManageMarkerCategoriesDialogProps } from './components/ManageMarkerCategoriesDialog';
|
||||||
import { isNullOrEmpty } from '@spfxappdev/utility';
|
import { isNullOrEmpty } from '@spfxappdev/utility';
|
||||||
|
import { Spinner, ISpinnerProps } from '@microsoft/office-ui-fabric-react-bundle';
|
||||||
|
|
||||||
export interface IMapPlugins {
|
export interface IMapPlugins {
|
||||||
searchBox: boolean;
|
searchBox: boolean;
|
||||||
|
@ -34,6 +33,7 @@ export interface IMapWebPartProps {
|
||||||
center: [number, number];
|
center: [number, number];
|
||||||
startZoom: number;
|
startZoom: number;
|
||||||
maxZoom: number;
|
maxZoom: number;
|
||||||
|
minZoom: number;
|
||||||
height: number;
|
height: number;
|
||||||
scrollWheelZoom: boolean;
|
scrollWheelZoom: boolean;
|
||||||
dragging: boolean;
|
dragging: boolean;
|
||||||
|
@ -49,11 +49,10 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
private _isDarkTheme: boolean = false;
|
private _isDarkTheme: boolean = false;
|
||||||
|
|
||||||
protected onInit(): Promise<void> {
|
protected onInit(): Promise<void> {
|
||||||
return super.onInit();
|
return super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
|
|
||||||
const element: React.ReactElement<IMapProps> = React.createElement(
|
const element: React.ReactElement<IMapProps> = React.createElement(
|
||||||
Map,
|
Map,
|
||||||
{
|
{
|
||||||
|
@ -61,6 +60,8 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
markerCategories: this.properties.markerCategories||[],
|
markerCategories: this.properties.markerCategories||[],
|
||||||
isEditMode: this.displayMode == DisplayMode.Edit,
|
isEditMode: this.displayMode == DisplayMode.Edit,
|
||||||
zoom: this.properties.startZoom,
|
zoom: this.properties.startZoom,
|
||||||
|
minZoom: this.properties.minZoom,
|
||||||
|
maxZoom: this.properties.maxZoom,
|
||||||
center: this.properties.center,
|
center: this.properties.center,
|
||||||
title: this.properties.title,
|
title: this.properties.title,
|
||||||
height: this.properties.height,
|
height: this.properties.height,
|
||||||
|
@ -91,9 +92,37 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
ReactDom.render(element, this.domElement);
|
ReactDom.render(element, this.domElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
|
protected onDisplayModeChanged(oldDisplayMode: DisplayMode): void {
|
||||||
// super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue)
|
this.reload();
|
||||||
// console.log()
|
}
|
||||||
|
|
||||||
|
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 {
|
private onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]): void {
|
||||||
|
@ -131,12 +160,15 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
{
|
{
|
||||||
groupName: strings.WebPartPropertyGroupMapSettings,
|
groupName: strings.WebPartPropertyGroupMapSettings,
|
||||||
groupFields: [
|
groupFields: [
|
||||||
// PropertyPaneToggle('plugins.searchBox', {
|
// PropertyPaneWebPartInformation({
|
||||||
// label: "searchBox"
|
// description: `<div class='wp-settings-info'>${strings.WebPartPropertySettingsInfoLabel}</div>`,
|
||||||
|
// key: 'Info_For_3f860b48-1dc3-496d-bd28-b145672289cc'
|
||||||
// }),
|
// }),
|
||||||
PropertyPaneWebPartInformation({
|
PropertyPaneSlider('minZoom', {
|
||||||
description: `<div class='wp-settings-info'>${strings.WebPartPropertySettingsInfoLabel}</div>`,
|
label: strings.WebPartPropertyMinZoomLabel,
|
||||||
key: 'Info_For_3f860b48-1dc3-496d-bd28-b145672289cc'
|
max: 30,
|
||||||
|
min: 0,
|
||||||
|
step: 1
|
||||||
}),
|
}),
|
||||||
PropertyPaneSlider('maxZoom', {
|
PropertyPaneSlider('maxZoom', {
|
||||||
label: strings.WebPartPropertyMaxZoomLabel,
|
label: strings.WebPartPropertyMaxZoomLabel,
|
||||||
|
@ -150,12 +182,6 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
max: 1200,
|
max: 1200,
|
||||||
step: 50
|
step: 50
|
||||||
}),
|
}),
|
||||||
PropertyPaneTextField('tileLayerUrl', {
|
|
||||||
label: strings.WebPartPropertyTileLayerUrlLabel
|
|
||||||
}),
|
|
||||||
PropertyPaneTextField('tileLayerAttribution', {
|
|
||||||
label: strings.WebPartPropertyTileLayerAttributionLabel
|
|
||||||
}),
|
|
||||||
PropertyPaneToggle('scrollWheelZoom', {
|
PropertyPaneToggle('scrollWheelZoom', {
|
||||||
label: strings.WebPartPropertyScrollWheelZoomLabel,
|
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,
|
isCollapsed: true,
|
||||||
groupName: strings.WebPartPropertyGroupPlugins,
|
groupName: strings.WebPartPropertyGroupPlugins,
|
||||||
groupFields: [
|
groupFields: [
|
||||||
|
// PropertyPaneToggle('plugins.searchBox', {
|
||||||
|
// label: "searchBox"
|
||||||
|
// }),
|
||||||
PropertyPaneToggle('plugins.markercluster', {
|
PropertyPaneToggle('plugins.markercluster', {
|
||||||
label: strings.WebPartPropertyPluginMarkerClusterLabel,
|
label: strings.WebPartPropertyPluginMarkerClusterLabel,
|
||||||
}),
|
}),
|
||||||
|
@ -216,7 +261,7 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneWebPartInformation({
|
PropertyPaneWebPartInformation({
|
||||||
description: `<h3>Author</h3>
|
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>
|
<h3>Version</h3>
|
||||||
${this.context.manifest.version}
|
${this.context.manifest.version}
|
||||||
<h3>Web Part Instance id</h3>
|
<h3>Web Part Instance id</h3>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
|
||||||
import ManageMarkerCategoriesDialog from './ManageMarkerCategoriesDialog';
|
import ManageMarkerCategoriesDialog from './ManageMarkerCategoriesDialog';
|
||||||
import { MarkerIcon } from './MarkerIcon';
|
import { MarkerIcon } from './MarkerIcon';
|
||||||
import * as strings from 'MapWebPartStrings';
|
import * as strings from 'MapWebPartStrings';
|
||||||
|
import { IconPicker } from '@src/components/iconPicker/IconPicker';
|
||||||
|
|
||||||
export interface IAddOrEditPanelProps {
|
export interface IAddOrEditPanelProps {
|
||||||
markerItem: IMarker;
|
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.state.markerItem.iconProperties.iconName = iconName;
|
||||||
this.setState({
|
this.setState({
|
||||||
markerItem: this.state.markerItem,
|
markerItem: this.state.markerItem,
|
||||||
isSaveButtonDisabled: false
|
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
|
<InlineColorPicker
|
||||||
label={strings.LabelIconColor}
|
label={strings.LabelIconColor}
|
||||||
|
|
|
@ -50,6 +50,7 @@ export interface IMapProps {
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
center?: [number, number];
|
center?: [number, number];
|
||||||
maxZoom?: number;
|
maxZoom?: number;
|
||||||
|
minZoom?: number;
|
||||||
title?: string;
|
title?: string;
|
||||||
height: number;
|
height: number;
|
||||||
dragging: boolean;
|
dragging: boolean;
|
||||||
|
|
|
@ -166,7 +166,7 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
|
||||||
<div className='spfxappdev-grid-col spfxappdev-sm3'>
|
<div className='spfxappdev-grid-col spfxappdev-sm3'>
|
||||||
<IconPicker
|
<IconPicker
|
||||||
defaultValue={categoryItem.iconProperties.iconName}
|
defaultValue={categoryItem.iconProperties.iconName}
|
||||||
onChange={(ev: any, name: string) => {
|
onIconChanged={(name: string) => {
|
||||||
this.state.markerCategories[index].iconProperties.iconName = name;
|
this.state.markerCategories[index].iconProperties.iconName = name;
|
||||||
this.validateForm();
|
this.validateForm();
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.display-mode .marker-type-none {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
.manage-categories-label {
|
.manage-categories-label {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #1190F4;
|
color: #1190F4;
|
||||||
|
@ -143,7 +148,7 @@
|
||||||
border-bottom: solid 1px #aeaeae;
|
border-bottom: solid 1px #aeaeae;
|
||||||
|
|
||||||
&:nth-child(odd) {
|
&:nth-child(odd) {
|
||||||
background: $ms-color-themeLight;
|
background: $ms-color-themeLighter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import "leaflet/dist/leaflet.css";
|
||||||
import "react-leaflet-markercluster/dist/styles.min.css";
|
import "react-leaflet-markercluster/dist/styles.min.css";
|
||||||
import * as L from 'leaflet';
|
import * as L from 'leaflet';
|
||||||
import { ContextualMenu, IContextualMenuItem, Panel, Dialog, IPanelProps, DefaultButton, PanelType, DialogType, DialogContent, Label, Separator, PrimaryButton } from 'office-ui-fabric-react';
|
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/StringExtensions';
|
||||||
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
|
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
|
||||||
import { DisplayMode } from '@microsoft/sp-core-library';
|
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> {
|
public render(): React.ReactElement<IMapProps> {
|
||||||
|
|
||||||
this.allLeafletMarker = {};
|
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 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);
|
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
|
<MapContainer
|
||||||
|
className={this.props.isEditMode ? "edit-mode" : "display-mode"}
|
||||||
zoomControl={isZoomControlEnabled}
|
zoomControl={isZoomControlEnabled}
|
||||||
center={this.props.center}
|
center={this.props.center}
|
||||||
zoom={this.props.zoom}
|
zoom={this.props.zoom}
|
||||||
maxZoom={this.props.maxZoom}
|
maxZoom={this.props.maxZoom}
|
||||||
|
minZoom={this.props.minZoom}
|
||||||
scrollWheelZoom={isScrollWheelZoomEnabled}
|
scrollWheelZoom={isScrollWheelZoomEnabled}
|
||||||
touchZoom={isScrollWheelZoomEnabled}
|
touchZoom={isScrollWheelZoomEnabled}
|
||||||
doubleClickZoom={isScrollWheelZoomEnabled}
|
doubleClickZoom={isScrollWheelZoomEnabled}
|
||||||
dragging={isDraggingEnabled}
|
dragging={isDraggingEnabled}
|
||||||
|
|
||||||
whenCreated={(map: L.Map) => {
|
whenCreated={(map: L.Map) => {
|
||||||
map.on("contextmenu", (ev: L.LeafletEvent) => {
|
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;
|
this.map = map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,12 +508,14 @@ export default class Map extends React.Component<IMapProps, IMapState> {
|
||||||
shadowSize: null,
|
shadowSize: null,
|
||||||
shadowAnchor: null,
|
shadowAnchor: null,
|
||||||
iconSize: new L.Point(27, 36),
|
iconSize: new L.Point(27, 36),
|
||||||
className: 'leaflet-div-icon'
|
className: cssClasses('leaflet-div-icon', `marker-type-${marker.type.toLowerCase()}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
markerIcon.createIcon = (oldIcon: HTMLElement) => {
|
markerIcon.createIcon = (oldIcon: HTMLElement) => {
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.classList.add("leaflet-marker-icon");
|
wrapper.classList.add("leaflet-marker-icon");
|
||||||
|
wrapper.classList.add(`marker-type-${marker.type.toLowerCase()}`);
|
||||||
|
|
||||||
wrapper.dataset.markerid = marker.id;
|
wrapper.dataset.markerid = marker.id;
|
||||||
|
|
||||||
wrapper.style.marginLeft = (markerIcon.options.iconAnchor[0] * -1) + "px";
|
wrapper.style.marginLeft = (markerIcon.options.iconAnchor[0] * -1) + "px";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
define([], function() {
|
define([], function() {
|
||||||
return {
|
return {
|
||||||
"WebPartPropertyGroupMapSettings": "General settings",
|
"WebPartPropertyGroupMapSettings": "General settings",
|
||||||
|
"WebPartPropertyGroupTileLayerSettings": "Tile Layer",
|
||||||
"WebPartPropertyGroupPlugins": "Plugins/Controls",
|
"WebPartPropertyGroupPlugins": "Plugins/Controls",
|
||||||
"WebPartPropertyGroupCategories": "Categories",
|
"WebPartPropertyGroupCategories": "Categories",
|
||||||
"WebPartPropertyGroupAbout": "About",
|
"WebPartPropertyGroupAbout": "About",
|
||||||
|
@ -9,13 +10,15 @@ define([], function() {
|
||||||
"WebPartPropertyPluginLegendLabel": "Show legend",
|
"WebPartPropertyPluginLegendLabel": "Show legend",
|
||||||
"WebPartPropertyButtonManageCategories": "Manage categories",
|
"WebPartPropertyButtonManageCategories": "Manage categories",
|
||||||
"WebPartPropertyPluginZoomControlLabel": "Show zoom control",
|
"WebPartPropertyPluginZoomControlLabel": "Show zoom control",
|
||||||
"WebPartPropertyScrollWheelZoomLabel": "Enable zoom on mouse wheel/touch",
|
"WebPartPropertyScrollWheelZoomLabel": "Enable zoom on mouse wheel/touch (only in display mode)",
|
||||||
"WebPartPropertyMapDraggingLabel": "Enable dragging in map",
|
"WebPartPropertyMapDraggingLabel": "Enable dragging in map (only in display mode)",
|
||||||
"WebPartPropertyShowPopUpLabel": "Show tooltip when hovering the marker",
|
"WebPartPropertyShowPopUpLabel": "Show tooltip when hovering the marker",
|
||||||
"WebPartPropertySettingsInfoLabel": "Most of these settings take effect only after a page refresh and only in display mode",
|
"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)",
|
"WebPartPropertyMinZoomLabel": "Minimum zoom level (zoom out)",
|
||||||
"WebPartPropertyHeightLabel": "Map height",
|
"WebPartPropertyMaxZoomLabel": "Maximum zoom level (zoom in, depends on Tile layer)",
|
||||||
"WebPartPropertyTileLayerUrlLabel": "Tile layer URL (Examples can be found here: https://leaflet-extras.github.io/leaflet-providers/preview/)",
|
"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",
|
"WebPartPropertyTileLayerAttributionLabel": "Tile layer attribution",
|
||||||
"ContextMenuAddNewMarkerLabel": "Add a new marker here",
|
"ContextMenuAddNewMarkerLabel": "Add a new marker here",
|
||||||
"ContextMenuSetStartPositionLabel": "Make this view as start position",
|
"ContextMenuSetStartPositionLabel": "Make this view as start position",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
declare interface IMapWebPartStrings {
|
declare interface IMapWebPartStrings {
|
||||||
WebPartPropertyGroupMapSettings: string;
|
WebPartPropertyGroupMapSettings: string;
|
||||||
|
WebPartPropertyGroupTileLayerSettings: string;
|
||||||
WebPartPropertyGroupPlugins: string;
|
WebPartPropertyGroupPlugins: string;
|
||||||
WebPartPropertyGroupCategories: string;
|
WebPartPropertyGroupCategories: string;
|
||||||
WebPartPropertyGroupAbout: string;
|
WebPartPropertyGroupAbout: string;
|
||||||
|
@ -12,8 +13,10 @@ declare interface IMapWebPartStrings {
|
||||||
WebPartPropertyMapDraggingLabel: string;
|
WebPartPropertyMapDraggingLabel: string;
|
||||||
WebPartPropertyShowPopUpLabel: string;
|
WebPartPropertyShowPopUpLabel: string;
|
||||||
WebPartPropertySettingsInfoLabel: string;
|
WebPartPropertySettingsInfoLabel: string;
|
||||||
|
WebPartPropertyMinZoomLabel: string;
|
||||||
WebPartPropertyMaxZoomLabel: string;
|
WebPartPropertyMaxZoomLabel: string;
|
||||||
WebPartPropertyHeightLabel: string;
|
WebPartPropertyHeightLabel: string;
|
||||||
|
WebPartPropertyTileLayerUrlInformationLabel: string;
|
||||||
WebPartPropertyTileLayerUrlLabel: string;
|
WebPartPropertyTileLayerUrlLabel: string;
|
||||||
WebPartPropertyTileLayerAttributionLabel: string;
|
WebPartPropertyTileLayerAttributionLabel: string;
|
||||||
ContextMenuAddNewMarkerLabel: string;
|
ContextMenuAddNewMarkerLabel: string;
|
||||||
|
|
Loading…
Reference in New Issue