Merge pull request #1 from SPFxAppDev/feature/iconpicker
Feature/iconpicker
This commit is contained in:
commit
f34d4c56b5
|
@ -20,3 +20,6 @@ npm run serve
|
|||
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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
.autocomplete {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
import * as React from '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';
|
||||
|
||||
|
||||
export interface IAutocompleteProps extends Omit<ITextFieldProps, "componentRef"> {
|
||||
showSuggestionsOnFocus?: boolean;
|
||||
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 {
|
||||
currentValue: string;
|
||||
isFlyoutVisible: boolean;
|
||||
}
|
||||
|
||||
export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompleteState> {
|
||||
|
||||
public state: IAutocompleteState = {
|
||||
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue,
|
||||
isFlyoutVisible: false,
|
||||
};
|
||||
|
||||
public static defaultProps: IAutocompleteProps = {
|
||||
showSuggestionsOnFocus: false,
|
||||
minValueLength: 3,
|
||||
calloutProps: {
|
||||
gapSpace: 0
|
||||
}
|
||||
};
|
||||
|
||||
private textFieldReference: ITextField = null;
|
||||
|
||||
private textFieldDomElement: HTMLInputElement = null;
|
||||
|
||||
private userIsTyping: boolean = false;
|
||||
|
||||
private lastValue: string = "";
|
||||
|
||||
private onUpdateValueText: string = "";
|
||||
|
||||
public render(): React.ReactElement<IAutocompleteProps> {
|
||||
|
||||
return (<>
|
||||
<TextField {...this.props}
|
||||
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) => {
|
||||
if(this.props.showSuggestionsOnFocus) {
|
||||
this.handleSuggestionListVisibility();
|
||||
}
|
||||
|
||||
if(isFunction(this.props.onFocus)) {
|
||||
this.props.onFocus(ev);
|
||||
}
|
||||
}}
|
||||
onBlur={(ev: any) => {
|
||||
|
||||
this.onTextFieldBlur();
|
||||
|
||||
if(isFunction(this.props.onBlur)) {
|
||||
this.props.onBlur(ev);
|
||||
}
|
||||
}}
|
||||
onChange={(ev: any, newValue: string) => {
|
||||
this.onValueChanged(ev, newValue);
|
||||
}}
|
||||
defaultValue={this.state.currentValue}
|
||||
/>
|
||||
|
||||
{this.renderSuggesstionsFlyout()}
|
||||
</>);
|
||||
}
|
||||
|
||||
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}
|
||||
directionalHintFixed={true}
|
||||
isBeakVisible={false}
|
||||
target={this.textFieldDomElement}
|
||||
onDismiss={(ev?: any) => {
|
||||
this.hideSuggesstionsFlyout();
|
||||
|
||||
if(isFunction(this.props.calloutProps.onDismiss)) {
|
||||
this.props.calloutProps.onDismiss(ev);
|
||||
}
|
||||
}}
|
||||
preventDismissOnScroll={true}
|
||||
directionalHint={DirectionalHint.bottomCenter}>
|
||||
{isFunction(this.props.onRenderSuggestions) && this.props.onRenderSuggestions()}
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
|
||||
private onValueChanged(ev: any, newValue: string): void {
|
||||
this.userIsTyping = true;
|
||||
|
||||
this.state.currentValue = newValue;
|
||||
this.setState({
|
||||
currentValue: newValue
|
||||
});
|
||||
|
||||
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 {
|
||||
let val = this.state.currentValue;
|
||||
|
||||
if(isNullOrEmpty(val)) {
|
||||
this.hideSuggesstionsFlyout();
|
||||
return;
|
||||
}
|
||||
|
||||
if(val.length < this.props.minValueLength) {
|
||||
this.hideSuggesstionsFlyout();
|
||||
return;
|
||||
}
|
||||
|
||||
let valueWasChanged = false;
|
||||
|
||||
if(!val.Equals(this.lastValue)) {
|
||||
this.userIsTyping = false;
|
||||
valueWasChanged = true;
|
||||
}
|
||||
|
||||
if(!valueWasChanged) {
|
||||
this.showSuggesstionsFlyout();
|
||||
return;
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
if(this.userIsTyping) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showSuggesstionsFlyout();
|
||||
|
||||
if(isFunction(this.props.onLoadSuggestions)) {
|
||||
this.props.onLoadSuggestions(this.state.currentValue);
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
private hideSuggesstionsFlyout(): void {
|
||||
this.setState({
|
||||
isFlyoutVisible: false
|
||||
});
|
||||
}
|
||||
|
||||
private showSuggesstionsFlyout(): void {
|
||||
this.setState({
|
||||
isFlyoutVisible: true
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import * as React from 'react';
|
||||
import styles from './IconPicker.module.scss';
|
||||
import { Icon, ITextField } from 'office-ui-fabric-react';
|
||||
import { allIcons } from './availableIcons';
|
||||
import { isNullOrEmpty, cssClasses, isFunction } from '@spfxappdev/utility';
|
||||
import { Autocomplete, IAutocompleteProps } from '@src/components/autocomplete/Autocomplete';
|
||||
|
||||
|
||||
export interface IIconPickerProps extends Omit<IAutocompleteProps, "onUpdated" | "onChange"> {
|
||||
enableDialogPicker?: boolean;
|
||||
dialogPickerIconName?: string;
|
||||
onIconChanged?(iconName: string): void;
|
||||
}
|
||||
|
||||
interface IIconPickerState {
|
||||
currentValue: string;
|
||||
}
|
||||
|
||||
export class IconPicker extends React.Component<IIconPickerProps, IIconPickerState> {
|
||||
|
||||
public state: IIconPickerState = {
|
||||
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue
|
||||
};
|
||||
|
||||
public static defaultProps: IIconPickerProps = {
|
||||
dialogPickerIconName: "GroupedList",
|
||||
enableDialogPicker: true,
|
||||
showSuggestionsOnFocus: false,
|
||||
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.setState({
|
||||
currentValue: newValue
|
||||
});
|
||||
}}
|
||||
onRenderSuggestions={() => {
|
||||
return this.renderSuggesstionsFlyout();
|
||||
}}
|
||||
iconProps={{
|
||||
iconName: this.state.currentValue
|
||||
}} />
|
||||
|
||||
|
||||
</>);
|
||||
}
|
||||
|
||||
private renderSuggesstionsFlyout(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className={styles["suggesstion"]}>
|
||||
{allIcons.Where(icon => icon.StartsWith(this.state.currentValue)).map((iconName: string): JSX.Element => {
|
||||
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>);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -34,7 +34,8 @@
|
|||
"markerCategories": [],
|
||||
"center": [51.505, -0.09],
|
||||
"startZoom": 13,
|
||||
"maxZoom": 50,
|
||||
"maxZoom": 30,
|
||||
"minZoom": 1,
|
||||
"dragging": true,
|
||||
"height": 400,
|
||||
"scrollWheelZoom": true,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -50,6 +50,7 @@ export interface IMapProps {
|
|||
zoom?: number;
|
||||
center?: [number, number];
|
||||
maxZoom?: number;
|
||||
minZoom?: number;
|
||||
title?: string;
|
||||
height: number;
|
||||
dragging: boolean;
|
||||
|
|
|
@ -11,6 +11,7 @@ import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
|
|||
import { IconButton } from '@microsoft/office-ui-fabric-react-bundle';
|
||||
import { MarkerIcon } from './MarkerIcon';
|
||||
import * as strings from 'MapWebPartStrings';
|
||||
import { IconPicker } from '@src/components/iconPicker/IconPicker';
|
||||
|
||||
export interface IManageMarkerCategoriesDialogProps {
|
||||
markerCategories: IMarkerCategory[];
|
||||
|
@ -163,12 +164,12 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
|
|||
/>
|
||||
</div>
|
||||
<div className='spfxappdev-grid-col spfxappdev-sm3'>
|
||||
<TextField
|
||||
defaultValue={categoryItem.iconProperties.iconName}
|
||||
onChange={(ev: any, name: string) => {
|
||||
<IconPicker
|
||||
defaultValue={categoryItem.iconProperties.iconName}
|
||||
onIconChanged={(name: string) => {
|
||||
this.state.markerCategories[index].iconProperties.iconName = name;
|
||||
this.validateForm();
|
||||
}}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='spfxappdev-grid-col spfxappdev-sm1'>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
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";
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue