catagories management and UI Updates

This commit is contained in:
Sergej Schwabauer 2022-03-02 18:09:24 +01:00
parent 64b7f9ebf1
commit 6dbad8548f
14 changed files with 3403 additions and 681 deletions

View File

@ -11,3 +11,6 @@ npm run serve
npm install -D @types/leaflet babel-loader @babel/core @babel/preset-env @babel/plugin-proposal-nullish-coalescing-operator
npm run serve
exit
npm run serve
npm install @pnp/spfx-controls-react --save --save-exact
npm run serve

View File

@ -13,6 +13,7 @@
},
"externals": {},
"localizedResources": {
"MapWebPartStrings": "lib/webparts/map/loc/{locale}.js"
"MapWebPartStrings": "lib/webparts/map/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
}
}

2688
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
"@microsoft/sp-office-ui-fabric-core": "1.14.0",
"@microsoft/sp-property-pane": "1.14.0",
"@microsoft/sp-webpart-base": "1.14.0",
"@pnp/spfx-controls-react": "3.6.0",
"@spfxappdev/utility": "^1.1.0",
"leaflet": "^1.7.1",
"office-ui-fabric-react": "7.174.1",

View File

@ -13,4 +13,9 @@
height: 14px;
border-radius: 2px;
}
&.disabled {
background: darkgrey;
cursor: not-allowed;
}
}

View File

@ -5,6 +5,7 @@ import { isset, isNullOrEmpty } from '@spfxappdev/utility';
export interface IInlineColorPickerProps extends IColorPickerProps {
label?: string;
isDisbaled?: boolean;
}
interface IInlineColorPickerState {
@ -17,6 +18,11 @@ export class InlineColorPicker extends React.Component<IInlineColorPickerProps,
isPickerVisible: false,
};
public static defaultProps: IInlineColorPickerProps = {
color: '#000000',
isDisbaled: false
}
private targetElement: HTMLDivElement = null;
public render(): React.ReactElement<IInlineColorPickerProps> {
@ -41,13 +47,18 @@ export class InlineColorPicker extends React.Component<IInlineColorPickerProps,
<Label>{this.props.label}</Label>
}
<div
className={styles['inline-color-picker']}
className={styles['inline-color-picker'] + ` ${this.props.isDisbaled?styles['disabled']:''}`}
ref={(r) => {
if(isset(r)) {
this.targetElement = r;
}
}}
onClick={() => {
if(this.props.isDisbaled) {
return;
}
this.setState({ isPickerVisible: true });
}}>
<div className={styles['inline-color-picker-inner']} style={customCss}></div>

View File

@ -12,17 +12,30 @@
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportsFullBleed": true,
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
"supportsThemeVariants": true,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"groupId": "142aa22c-9004-41c2-9821-2d78eed048d1", // SPFx App Dev
"group": { "default": "Other" },
"title": { "default": "Map" },
"description": { "default": "Map description" },
"officeFabricIconFontName": "Page",
"officeFabricIconFontName": "MapPin",
"properties": {
"description": "Map"
"title": "Map",
"markerItems": [],
"markerCategories": [],
"center": [51.505, -0.09],
"startZoom": 13,
"maxZoom": 50,
"plugins": {
"searchBox": false,
"markercluster": false,
"legend": false,
"zoomControl": true,
"scrollWheelZoom": false
}
}
}]
}

View File

@ -3,7 +3,12 @@ import * as ReactDom from 'react-dom';
import { DisplayMode, Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
PropertyPaneTextField,
PropertyPaneToggle,
PropertyPaneSlider,
PropertyPaneButton,
PropertyPaneLabel
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
@ -12,10 +17,23 @@ import * as strings from 'MapWebPartStrings';
import Map from './components/Map';
import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps';
export interface IMapPlugins {
searchBox: boolean;
markercluster: boolean;
legend: boolean;
zoomControl: boolean;
scrollWheelZoom: boolean;
}
export interface IMapWebPartProps {
markerItems: IMarker[];
markerCategories: IMarkerCategory[];
title: string;
center: [number, number];
startZoom: number;
maxZoom: number;
height: number;
plugins: IMapPlugins;
}
export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> {
@ -27,58 +45,50 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
this._environmentMessage = this._getEnvironmentMessage();
return super.onInit();
}
public render(): void {
const dummyData: IMarker = {
id: "5828b794-0c76-4962-9faa-95e89aea6c37",
latitude: 49.318121,
longitude: 10.624094,
type: "Panel",
categoryId: "00000000-0000-0000-0000-000000000000",
markerClickProps: {
headerText: "",
content: ""
},
iconProperties: {
markerColor: "red",
iconName: "PageLink",
iconColor: "#000"
},
popuptext: "Hello"
}
const dummyData2: IMarker = {
id: "5828b794-0c76-4962-9faa-95e89aea6c37",
latitude: 49.508121,
longitude: 10.824094,
type: "None",
categoryId: "5828b794-0c76-4962-9faa-95e89aea6123"
}
const dummyCategory: IMarkerCategory = {
id: "5828b794-0c76-4962-9faa-95e89aea6123",
name: "teeeeest",
iconProperties: {
markerColor: "#000",
iconName: "Installation",
iconColor: "#fff"
},
}
console.log(this.properties);
const element: React.ReactElement<IMapProps> = React.createElement(
Map,
{
markerItems: this.properties.markerItems||[dummyData, dummyData2],
markerCategories: this.properties.markerCategories||[dummyCategory],
isEditMode: this.displayMode == DisplayMode.Edit
markerItems: this.properties.markerItems||[],
markerCategories: this.properties.markerCategories||[],
isEditMode: this.displayMode == DisplayMode.Edit,
zoom: this.properties.startZoom,
center: this.properties.center,
title: this.properties.title,
height: this.properties.height,
plugins: this.properties.plugins,
onMarkerCollectionChanged: (markerItems: IMarker[]) => {
this.properties.markerItems = markerItems;
},
onMarkerCategoriesChanged: (markerCategories: IMarkerCategory[]) => {
this.properties.markerCategories = markerCategories;
},
onStartViewSet: (zoomLevel: number, lat: number, lng: number) => {
this.properties.startZoom = zoomLevel;
this.properties.center = [lat, lng];
},
onTitleUpdate: (value: string) => {
this.properties.title = value;
}
}
);
ReactDom.render(element, this.domElement);
}
// protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
// super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue)
// console.log()
// }
private _getEnvironmentMessage(): string {
if (!!this.context.sdks.microsoftTeams) { // running in Teams
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
@ -121,8 +131,26 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
PropertyPaneToggle('plugins.searchBox', {
label: "searchBox"
}),
PropertyPaneToggle('plugins.markercluster', {
label: "markercluster"
}),
PropertyPaneToggle('plugins.legend', {
label: "legend"
}),
PropertyPaneToggle('plugins.zoomControl', {
label: "zoomControl"
}),
PropertyPaneToggle('plugins.scrollWheelZoom', {
label: "scrollWheelZoom",
}),
PropertyPaneButton('groups', {
text: "Manage Groups",
onClick: (val: any) => {
return null;
}
})
]
}

View File

@ -0,0 +1,326 @@
import * as React from 'react';
import { IMarker, IMarkerCategory, MarkerType } from './IMapProps';
import styles from './Map.module.scss';
import { clone } from '@microsoft/sp-lodash-subset';
import { Icon, Panel, Dialog, TextField, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType, Label } from 'office-ui-fabric-react';
import { Guid } from '@microsoft/sp-core-library';
import { isNullOrEmpty, isFunction } from '@spfxappdev/utility';
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker';
import { RichText } from "@pnp/spfx-controls-react/lib/RichText";
import '@spfxappdev/utility/lib/extensions/StringExtensions';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import ManageMarkerCategoriesDialog from './ManageMarkerCategoriesDialog';
import { MarkerIcon } from './MarkerIcon';
export interface IAddOrEditPanelProps {
markerItem: IMarker;
markerCategories: IMarkerCategory[];
onDismiss();
onMarkerChanged(markerItem: IMarker, isNewMarker: boolean);
onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]);
}
interface IAddOrEditPanelState {
markerItem: IMarker;
markerCategories: IMarkerCategory[];
isSaveButtonDisabled: boolean;
isManageCategoriesDialogVisible: boolean;
}
export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps, IAddOrEditPanelState> {
public state: IAddOrEditPanelState = {
markerItem: clone(this.props.markerItem),
markerCategories: clone(this.props.markerCategories),
isSaveButtonDisabled: true,
isManageCategoriesDialogVisible: false
};
private readonly isNewMarker: boolean;
private readonly headerText: string;
private markerTypeOptions: IChoiceGroupOption[] = [
{ key: 'Panel', text: 'Panel', iconProps: { iconName: 'SidePanel' } },
{ key: 'Dialog', text: 'Dialog', iconProps: { iconName: 'Favicon' } },
{ key: 'Url', text: 'Url', iconProps: { iconName: 'Link' } },
{ key: 'None', text: 'None (not clickable)', iconProps: { iconName: 'FieldEmpty' } },
];
private urlOptions: IChoiceGroupOption[] = [
{ key: '_self', text: 'Open in same window' },
{ key: '_blank', text: 'Open in new window' },
{ key: 'embedded', text: 'Embedded (Dialog/iFrame)' },
];
constructor(props: IAddOrEditPanelProps) {
super(props);
this.isNewMarker = this.props.markerItem.id.Equals(Guid.empty.toString());
this.headerText = !this.isNewMarker ? "Bearbeiten" : "Neu";
}
public render(): React.ReactElement<IAddOrEditPanelProps> {
const selectedCatId: string = this.state.markerCategories.Contains(cat => cat.id.Equals(this.state.markerItem.categoryId)) ? this.state.markerItem.categoryId : Guid.empty.toString();
return (
<Panel
type={PanelType.medium}
isOpen={true}
onDismiss={() => { this.onConfigPanelDismiss() }}
headerText={this.headerText}
closeButtonAriaLabel="Close"
onRenderFooterContent={(props: IPanelProps) => {
return this.renderPanelFooter();
}}
// Stretch panel content to fill the available height so the footer is positioned
// at the bottom of the page
isFooterAtBottom={true}
>
<Label>
Category
<span
onClick={() => {
this.setState({
isManageCategoriesDialogVisible: true
});
}}
className='manage-categories-label'>
(Manage)
</span>
</Label>
<Dropdown
placeholder="Select a category"
defaultSelectedKey={selectedCatId}
onChange={(ev: any, option: IDropdownOption) => {
this.state.markerItem.categoryId = option.key.toString();
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}}
options={this.categoryOptions}
/>
<ChoiceGroup
label="Type of marker (on click)"
defaultSelectedKey={this.state.markerItem.type}
onChange={(ev: any, option: IChoiceGroupOption) => {
this.state.markerItem.type = option.key.toString() as MarkerType;
// if( this.state.markerItem.type == "None") {
// this.state.markerItem.markerClickProps = undefined;
// }
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}}
options={this.markerTypeOptions} />
{this.renderNonCategorySettings()}
{this.renderUrlSettings()}
{this.renderPanelOrDialogSettings()}
{this.renderManageCategoriesDialog()}
</Panel>
);
}
private renderPanelFooter(): JSX.Element {
return (<div>
<PrimaryButton disabled={this.state.isSaveButtonDisabled} onClick={() => {
if(this.isNewMarker) {
this.state.markerItem.id = Guid.newGuid().toString();
}
this.onSaveMarkerClick(this.state.markerItem);
}}>
Save
</PrimaryButton>
<DefaultButton onClick={() => { this.onConfigPanelDismiss(); }}>Cancel</DefaultButton>
</div>);
}
private renderNonCategorySettings(): JSX.Element {
if(this.state.markerCategories.Contains(cat => cat.id.Equals(this.state.markerItem.categoryId))) {
return (<></>);
}
return (
<>
<InlineColorPicker
label='Marker Color'
alphaType='none'
color={getColorFromString(this.state.markerItem.iconProperties.markerColor)}
onChange={(ev: any, color: IColor) => {
this.state.markerItem.iconProperties.markerColor = "#" + color.hex;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}}
/>
<TextField label='Icon' description='leaf blank for none' 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
});
}} />
<InlineColorPicker
label='Icon Color'
alphaType='none'
color={getColorFromString(this.state.markerItem.iconProperties.iconColor)}
onChange={(ev: any, color: IColor) => {
this.state.markerItem.iconProperties.iconColor = "#" + color.hex;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}}
isDisbaled={isNullOrEmpty(this.state.markerItem.iconProperties.iconName)}
/>
<TextField label='Popup Text' description='leaf blank for none' defaultValue={this.state.markerItem.popuptext} onChange={(ev: any, popuptext: string) => {
this.state.markerItem.popuptext = popuptext;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}} />
<Label>Vorschau</Label>
<div style={{position: "relative", height: "36px", }}>
<div style={{position: "absolute"}}>
<MarkerIcon {...this.state.markerItem.iconProperties} />
</div>
</div>
</>
);
}
private renderPanelOrDialogSettings(): JSX.Element {
if(!(this.state.markerItem.type == "Dialog" || this.state.markerItem.type == "Panel")) {
return (<></>);
}
return (<>
<TextField label='Panel Header' defaultValue={this.state.markerItem.markerClickProps.content.headerText} onChange={(ev: any, headerText: string) => {
this.state.markerItem.markerClickProps.content.headerText = headerText;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}} />
<Label>Content</Label>
<RichText isEditMode={true} value={this.state.markerItem.markerClickProps.content.html} onChange={(content: string): string => {
this.state.markerItem.markerClickProps.content.html = content;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
return content;
}} />
</>)
}
private renderUrlSettings(): JSX.Element {
if(this.state.markerItem.type != "Url") {
return (<></>);
}
return (
<>
<TextField label='Url' type='url' defaultValue={this.state.markerItem.markerClickProps.url.href} onChange={(ev: any, url: string) => {
this.state.markerItem.markerClickProps.url.href = url;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}} />
<ChoiceGroup
defaultSelectedKey={this.state.markerItem.markerClickProps.url.target}
options={this.urlOptions}
onChange={(ev: any, option: IChoiceGroupOption) => {
(this.state.markerItem.markerClickProps.url.target as any) = option.key;
this.setState({
markerItem: this.state.markerItem,
isSaveButtonDisabled: false
});
}}
/>
</>
);
}
private renderManageCategoriesDialog(): JSX.Element {
if(!this.state.isManageCategoriesDialogVisible) {
return (<></>);
}
return (
<>
<ManageMarkerCategoriesDialog
markerCategories={this.props.markerCategories}
onDismiss={() => {
this.setState({
isManageCategoriesDialogVisible: false
});
}}
onMarkerCategoriesChanged={(markerCategories: IMarkerCategory[]) => {
this.setState({
isManageCategoriesDialogVisible: false,
markerCategories: markerCategories
});
if(isFunction(this.props.onMarkerCategoriesChanged)) {
this.props.onMarkerCategoriesChanged(markerCategories);
}
}}
/>
</>
);
}
private onConfigPanelDismiss(): void {
if(isFunction(this.props.onDismiss)) {
this.props.onDismiss();
}
}
private onSaveMarkerClick(marker: IMarker): void {
if(isFunction(this.props.onMarkerChanged)) {
this.props.onMarkerChanged(this.state.markerItem, this.isNewMarker);
}
}
private get categoryOptions(): IDropdownOption[] {
const categories: IDropdownOption[] = [
{ key: Guid.empty.toString(), text: 'None' }
];
this.state.markerCategories.forEach((category: IMarkerCategory) => {
categories.push({ key: category.id, text: category.name });
});
return categories;
}
}

View File

@ -1,21 +1,22 @@
import { Guid } from '@microsoft/sp-core-library';
import * as L from 'leaflet';
import { IMapPlugins } from '../MapWebPart';
export type MarkerType = "Panel"|"Dialog"|"Url"|"None";
export interface IMarkerClickProps {
url: IMarkerUrlProperties;
content: IMarkerContentProperties
}
export type MarkerTypePanel = {
headerText: string;
content: string;
export interface IMarkerUrlProperties {
href: string;
target: '_self'|'_blank'|'embedded';
};
export type MarkerTypeUrl = {
url: string;
};
export type MarkerTypeDialog = {
export interface IMarkerContentProperties {
headerText: string;
content?: string;
url?: string;
html: string;
};
export interface IMarkerIcon {
@ -39,15 +40,40 @@ export interface IMarker {
categoryId: string;
iconProperties?: IMarkerIcon;
popuptext?: string;
markerClickProps?: MarkerTypePanel|MarkerTypeUrl|MarkerTypeDialog;
markerClickProps?: IMarkerClickProps;
}
export interface IMapProps {
markerItems: IMarker[];
markerCategories: IMarkerCategory[];
onMarkerCollectionChanged?(markerItems: IMarker[]);
onMarkerCategoriesChanged?(markerCategories: IMarkerCategory[]);
isEditMode: boolean;
zoom?: number;
center?: [number, number];
maxZoom?: number;
title?: string;
height: number;
plugins: IMapPlugins;
onMarkerCollectionChanged(markerItems: IMarker[]);
onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]);
onStartViewSet(zoomLevel: number, lat: number, lng: number);
onTitleUpdate?: (value: string) => void;
}
export const emptyMarkerItem: IMarker = {
id: Guid.empty.toString(),
latitude: 0,
longitude: 0,
type: "Panel",
markerClickProps: {
url: { href: "", target: '_blank' },
content: { html: '', headerText: '' }
},
categoryId: Guid.empty.toString(),
iconProperties: {
markerColor: "#000000",
iconName: "",
iconColor: "#000000"
},
popuptext: null
};

View File

@ -0,0 +1,206 @@
import * as React from 'react';
import { IMarker, IMarkerCategory, MarkerType } from './IMapProps';
import styles from './Map.module.scss';
import { clone } from '@microsoft/sp-lodash-subset';
import { Icon, Panel, Dialog, TextField, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType, Label, DialogFooter, DialogContent, DialogType } from 'office-ui-fabric-react';
import { Guid } from '@microsoft/sp-core-library';
import { isNullOrEmpty, isFunction } from '@spfxappdev/utility';
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker';
import { RichText } from "@pnp/spfx-controls-react/lib/RichText";
import '@spfxappdev/utility/lib/extensions/StringExtensions';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import { IconButton } from '@microsoft/office-ui-fabric-react-bundle';
import { MarkerIcon } from './MarkerIcon';
export interface IManageMarkerCategoriesDialogProps {
markerCategories: IMarkerCategory[];
onDismiss();
onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]);
}
interface IManageMarkerCategoriesDialogState {
markerCategories: IMarkerCategory[];
isSaveButtonDisabled: boolean;
isNewFormVisible: boolean;
}
export default class ManageMarkerCategoriesDialog extends React.Component<IManageMarkerCategoriesDialogProps, IManageMarkerCategoriesDialogState> {
public state: IManageMarkerCategoriesDialogState = {
markerCategories: clone(this.props.markerCategories),
isSaveButtonDisabled: false,
isNewFormVisible: false
};
constructor(props: IManageMarkerCategoriesDialogProps) {
super(props);
}
componentDidMount(): void {
this.validateForm();
}
public render(): React.ReactElement<IManageMarkerCategoriesDialogProps> {
return (
<Dialog
hidden={false}
onDismiss={() => { this.props.onDismiss(); }}
dialogContentProps={{
title: "Manage Categories",
type: DialogType.close
}}
minWidth={800}
modalProps={{
isBlocking: true,
className: "categories-dialog"
}}
>
<DialogContent>
<div className='spfxappdev-grid'>
<div className='spfxappdev-grid-row grid-header'>
<div className='spfxappdev-grid-col spfxappdev-sm1'></div>
<div className='spfxappdev-grid-col spfxappdev-sm3'>Name</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'>Marker color</div>
<div className='spfxappdev-grid-col spfxappdev-sm3'>Icon</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'>Icon Color</div>
<div className='spfxappdev-grid-col spfxappdev-sm2'>Tooltip text</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'></div>
</div>
{this.state.markerCategories.map((cat: IMarkerCategory, index: number): JSX.Element => {
return (<div key={cat.id} className='spfxappdev-grid-row categories-grid' data-catid={cat.id}>
{this.renderForm(cat, index)}
</div>)
})}
<div className='spfxappdev-grid-row grid-footer'>
<div className='spfxappdev-grid-col spfxappdev-sm12'>
<PrimaryButton onClick={() => {
this.onAddNewCatagoryButtonClick();
}}>Add</PrimaryButton>
</div>
</div>
</div>
</DialogContent>
<DialogFooter>
<PrimaryButton
onClick={() => {
if(isFunction(this.props.onMarkerCategoriesChanged)) {
this.props.onMarkerCategoriesChanged(this.state.markerCategories);
}
}}
text="Save"
disabled={this.state.isSaveButtonDisabled}
/>
<DefaultButton onClick={() => {
this.props.onDismiss();
}} text="Cancel" />
</DialogFooter>
</Dialog>);
}
private renderForm(categoryItem: IMarkerCategory, index: number): JSX.Element {
return (
<>
<div className='spfxappdev-grid-col spfxappdev-sm1'>
<IconButton iconProps={{iconName: "Delete"}} onClick={() => {
this.state.markerCategories.RemoveAt(index);
this.validateForm();
}} />
</div>
<div className='spfxappdev-grid-col spfxappdev-sm3'>
<TextField
required={true}
defaultValue={categoryItem.name}
onChange={(ev: any, name: string) => {
this.state.markerCategories[index].name = name;
this.validateForm();
}}
/>
</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'>
<InlineColorPicker
alphaType='none'
color={getColorFromString(categoryItem.iconProperties.markerColor)}
onChange={(ev: any, color: IColor) => {
this.state.markerCategories[index].iconProperties.markerColor = "#" + color.hex;
this.validateForm();
}}
/>
</div>
<div className='spfxappdev-grid-col spfxappdev-sm3'>
<TextField
defaultValue={categoryItem.iconProperties.iconName}
onChange={(ev: any, name: string) => {
this.state.markerCategories[index].iconProperties.iconName = name;
this.validateForm();
}}
/>
</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'>
<InlineColorPicker
alphaType='none'
color={getColorFromString(categoryItem.iconProperties.iconColor)}
onChange={(ev: any, color: IColor) => {
this.state.markerCategories[index].iconProperties.iconColor = "#" + color.hex;
this.validateForm();
}}
isDisbaled={isNullOrEmpty(categoryItem.iconProperties.iconName)}
/>
</div>
<div className='spfxappdev-grid-col spfxappdev-sm2'>
<TextField
defaultValue={categoryItem.popuptext}
onChange={(ev: any, popuptext: string) => {
this.state.markerCategories[index].popuptext = popuptext;
this.validateForm();
}}
/>
</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'>
<div style={{position: "absolute"}}>
<MarkerIcon {...categoryItem.iconProperties} />
</div>
</div>
</>
);
}
private validateForm(): void {
const isSaveBtnDisabled = this.state.markerCategories.Contains(cat => isNullOrEmpty(cat.name) || isNullOrEmpty(cat.iconProperties.markerColor));
this.setState({
markerCategories: this.state.markerCategories,
isSaveButtonDisabled: isSaveBtnDisabled
});
}
private onAddNewCatagoryButtonClick(): void {
this.state.markerCategories.push(this.createNewCatagoryItem());
this.validateForm();
}
private createNewCatagoryItem(): IMarkerCategory {
const category: IMarkerCategory = {
id: Guid.newGuid().toString(),
name: "",
iconProperties: {
markerColor: "#000",
iconName: "",
iconColor: "#fff"
}
};
return category;
}
}

View File

@ -1,5 +1,5 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.map {
@ -14,5 +14,156 @@
left: 8px;
top: 5px;
color: #fff;
i {
font-size: 12px;
}
}
.manage-categories-label {
font-size: 10px;
color: #1190F4;
padding-left: 5px;
cursor: pointer;
}
.spfxappdev-grid {
box-sizing: border-box;
zoom: 1;
padding: 0 8px;
&::before,
&::after {
display: table;
content: '';
line-height: 0;
-webkit-box-sizing: inherit;
box-sizing: inherit;
}
&::after {
clear: both;
}
&-row {
margin: 0 -8px;
box-sizing: border-box;
zoom: 1;
&::before,
&::after {
display: table;
content: '';
line-height: 0;
-webkit-box-sizing: inherit;
box-sizing: inherit;
}
&::after {
clear: both;
}
}
&-col {
position: relative;
min-height: 1px;
// padding-left: 8px;
// padding-right: 8px;
box-sizing: border-box;
float: left;
}
}
.spfxappdev-sm1 {
width: 8.33%
}
.spfxappdev-sm2 {
width: 16.66%;
}
.spfxappdev-sm3 {
width: 25%;
}
.spfxappdev-sm4 {
width: 33.33%;
}
.spfxappdev-sm5 {
width: 41.66%;
}
.spfxappdev-sm6 {
width: 50%;
}
.spfxappdev-sm7 {
width: 58.33%;
}
.spfxappdev-sm8 {
width: 66.66%;
}
.spfxappdev-sm9 {
width:75%;
}
.spfxappdev-sm10 {
width: 83.33%;
}
.spfxappdev-sm11 {
width: 91.66%;
}
.spfxappdev-sm12 {
width: 100%;
}
.grid-header {
background: $ms-color-themePrimary;
color: #fff;
font-weight: 600;
text-align: center;
}
.grid-footer {
margin-top: 10px
}
.categories-grid {
.spfxappdev-grid-col {
padding: 10px 2px;
}
&.spfxappdev-grid-row {
border-bottom: solid 1px #aeaeae;
&:nth-child(odd) {
background: $ms-color-themeLight;
}
}
}
.iframe-dialog,
.categories-dialog {
.ms-Dialog-content .ms-Dialog-header {
display: none;
}
}
.iframe-dialog {
iframe {
border: 0;
width: 100%;
height: 100%;
}
}
.ql-editor[contenteditable='true'] {
border: solid 1px $ms-color-themeDarker !important;
}
}

View File

@ -1,21 +1,27 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import styles from './Map.module.scss';
import { IMapProps, IMarker, IMarkerCategory, IMarkerIcon, MarkerType, MarkerTypeDialog, MarkerTypePanel, MarkerTypeUrl } from './IMapProps';
import { IMapProps, IMarker, IMarkerCategory, IMarkerIcon, MarkerType, IMarkerClickProps, IMarkerUrlProperties, IMarkerContentProperties, emptyMarkerItem } from './IMapProps';
import { clone } from '@microsoft/sp-lodash-subset';
import { MapContainer, TileLayer, Marker, Popup, Tooltip } from 'react-leaflet';
import "leaflet/dist/leaflet.css";
import * as L from 'leaflet';
import { Icon, ContextualMenu, ContextualMenuItemType, IContextualMenuItem, Panel, Dialog, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType } from 'office-ui-fabric-react';
import { randomString, isset, isNullOrEmpty, getDeepOrDefault } from '@spfxappdev/utility';
import { Icon, ContextualMenu, ContextualMenuItemType, IContextualMenuItem, Panel, Dialog, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType, DialogType, DialogContent } from 'office-ui-fabric-react';
import { randomString, isset, isNullOrEmpty, getDeepOrDefault, cssClasses } from '@spfxappdev/utility';
import '@spfxappdev/utility/lib/extensions/StringExtensions';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import { Guid } from '@microsoft/sp-core-library';
import { DisplayMode, Guid } from '@microsoft/sp-core-library';
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker'
import { TextField } from '@microsoft/office-ui-fabric-react-bundle';
import { RichText } from "@pnp/spfx-controls-react/lib/RichText";
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import AddOrEditPanel from './AddOrEditPanel';
import { isFunction } from 'lodash';
import { MarkerIcon } from './MarkerIcon';
interface IMapState {
markerItems: IMarker[];
markerCategories: IMarkerCategory[];
rightMouseTarget?: any;
showAddOrEditMarkerPanel: boolean;
currentMarker?: IMarker;
@ -26,6 +32,7 @@ export default class Map extends React.Component<IMapProps, IMapState> {
public state: IMapState = {
markerItems: clone(this.props.markerItems),
markerCategories: clone(this.props.markerCategories),
showAddOrEditMarkerPanel: false,
showClickContent: false
};
@ -35,10 +42,17 @@ export default class Map extends React.Component<IMapProps, IMapState> {
private menuItems: IContextualMenuItem[] = [
{
key: 'newItem',
text: 'New',
text: 'Add a new marker',
onClick: () => {
this.onCreateNewMarkerContextMenuItemClick();
}
},
{
key: 'setStartView',
text: 'Make this view as start position',
onClick: () => {
this.onSetStartView();
}
}
];
@ -49,20 +63,24 @@ export default class Map extends React.Component<IMapProps, IMapState> {
constructor(props: IMapProps) {
super(props);
props.markerCategories.forEach((category: IMarkerCategory) => {
this.allCatagories[category.id] = category;
});
this.setAllCatagoriesDictionary();
}
public render(): React.ReactElement<IMapProps> {
//
return (
<div className={styles.map} onContextMenu={(ev: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
<div className={styles.map}>
<WebPartTitle displayMode={this.props.isEditMode?DisplayMode.Edit:DisplayMode.Read}
title={this.props.title}
updateProperty={this.props.onTitleUpdate} />
}}>
<MapContainer center={[49.318121, 10.624094]} zoom={13} maxZoom={500} whenCreated={(map: L.Map) => {
<MapContainer
zoomControl={getDeepOrDefault<boolean>(this.props, "plugins.zoomControl", true)}
center={this.props.center}
zoom={this.props.zoom}
maxZoom={this.props.maxZoom}
whenCreated={(map: L.Map) => {
map.on("contextmenu", (ev: L.LeafletEvent) => {
this.lastLatLngRightClickPosition = (ev as any).latlng;
@ -73,15 +91,43 @@ export default class Map extends React.Component<IMapProps, IMapState> {
});
this.map = map;
}} style={{
height: "400px"
}}>
}
}
style={{height: "400px"}}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{this.renderMarker()}
</MapContainer>
{this.renderLegend()}
<ContextualMenu
items={this.menuItems}
hidden={typeof this.state.rightMouseTarget == "undefined"}
target={this.state.rightMouseTarget}
onItemClick={() => {
}}
onDismiss={() => {
this.setState({
rightMouseTarget: undefined
});
}}
/>
{this.showAddOrEditMarkerPanel()}
{this.showClickContent()}
</div>
);
}
private renderMarker(): JSX.Element {
return (
<>
{this.state.markerItems.map((marker: IMarker, index: number): JSX.Element => {
const useCategory: boolean = isset(this.allCatagories[marker.categoryId]);
const markerCategory: IMarkerCategory = useCategory ? this.allCatagories[marker.categoryId] : null;
@ -118,52 +164,86 @@ height: "400px"
</Marker>
);
})}
<ContextualMenu
items={this.menuItems}
hidden={typeof this.state.rightMouseTarget == "undefined"}
target={this.state.rightMouseTarget}
onItemClick={() => {
}}
onDismiss={() => {
this.setState({
rightMouseTarget: undefined
});
}}
/>
{this.showAddOrEditMarkerPanel()}
{this.showClickContent()}
</MapContainer>
</div>
</>
);
}
private renderLegend(): JSX.Element {
if(!getDeepOrDefault<boolean>(this.props, "plugins.legend", false) || isNullOrEmpty(this.state.markerCategories)) {
return (<></>);
}
return (<>TBD</>);
}
private showClickContent(): JSX.Element {
if(!this.state.showClickContent || isNullOrEmpty(this.state.currentMarker)) {
return (<></>);
}
if(this.state.currentMarker.type == "None") {
return (<></>);
}
if(this.state.currentMarker.type == "Url" && this.state.currentMarker.markerClickProps.url.target != "embedded") {
window.open(this.state.currentMarker.markerClickProps.url.href, this.state.currentMarker.markerClickProps.url.target);
return (<></>);
}
if (this.state.currentMarker.type == "Panel") {
return (<Panel
type={PanelType.medium}
isOpen={true}
onDismiss={() => { this.onContentPanelDismiss() }}
headerText={(this.state.currentMarker.markerClickProps as MarkerTypePanel).headerText}
onDismiss={() => { this.onContentPanelOrDialogDismiss() }}
headerText={this.state.currentMarker.markerClickProps.content.headerText}
closeButtonAriaLabel="Close"
onRenderFooterContent={(props: IPanelProps) => {
return (<div>
<DefaultButton onClick={() => { this.onContentPanelDismiss(); }}>Close</DefaultButton>
<DefaultButton onClick={() => { this.onContentPanelOrDialogDismiss(); }}>Close</DefaultButton>
</div>);
}}
// Stretch panel content to fill the available height so the footer is positioned
// at the bottom of the page
isFooterAtBottom={true}
>
{(this.state.currentMarker.markerClickProps as MarkerTypePanel).headerText}
<RichText isEditMode={false} value={this.state.currentMarker.markerClickProps.content.html} />
</Panel>);
}
const width: number = window.innerWidth - 100;
const height: number = window.innerHeight - 300;
let dialogWidth = 900;
if(width < dialogWidth || this.state.currentMarker.type == "Url") {
dialogWidth = width;
}
return (
<Dialog
hidden={false}
onDismiss={() => { this.onContentPanelOrDialogDismiss(); }}
dialogContentProps={{
title: this.state.currentMarker.markerClickProps.content.headerText,
type: DialogType.close
}}
minWidth={dialogWidth}
modalProps={{
isBlocking: true,
className: "iframe-dialog",
}}
>
<DialogContent>
{this.state.currentMarker.type == "Dialog" && <RichText isEditMode={false} value={this.state.currentMarker.markerClickProps.content.html} />}
{this.state.currentMarker.type == "Url" &&
<div style={{height: `${height}px`}}>
<iframe src={this.state.currentMarker.markerClickProps.url.href}></iframe>
</div>
}
</DialogContent>
</Dialog>
);
}
private showAddOrEditMarkerPanel(): JSX.Element {
@ -172,190 +252,46 @@ height: "400px"
return (<></>);
}
const headerText: string = !this.state.currentMarker.id.Equals(Guid.empty.toString()) ? "Bearbeiten" : "Neu";
const markerTypeOptions: IChoiceGroupOption[] = [
{ key: 'Panel', text: 'Panel', iconProps: { iconName: 'SidePanel' } },
{ key: 'Dialog', text: 'Dialog', iconProps: { iconName: 'Favicon' } },
{ key: 'Url', text: 'Url', iconProps: { iconName: 'Link' } },
{ key: 'None', text: 'None (not clickable)', iconProps: { iconName: 'FieldEmpty' } },
];
const categoryOptions: IDropdownOption[] = [
{ key: Guid.empty.toString(), text: 'None' }
];
this.props.markerCategories.forEach((category: IMarkerCategory) => {
categoryOptions.push({ key: category.id, text: category.name });
});
return (
<Panel
type={PanelType.medium}
isOpen={this.state.showAddOrEditMarkerPanel}
onDismiss={() => { this.onConfigPanelDismiss() }}
headerText={headerText}
closeButtonAriaLabel="Close"
onRenderFooterContent={(props: IPanelProps) => {
return (<div>
<PrimaryButton onClick={() => {
this.state.currentMarker.id = Guid.newGuid().toString();
// this.onCreateNewMarkerClick(clone(this.state.currentMarker));
this.onCreateNewMarkerClick(this.state.currentMarker);
// this.state.currentMarker = null;
<AddOrEditPanel
markerCategories={this.state.markerCategories}
markerItem={this.state.currentMarker}
onDismiss={() => { this.onConfigPanelDismiss(); }}
onMarkerCategoriesChanged={(markerCategories: IMarkerCategory[]) => {
this.state.markerCategories = markerCategories;
if(isFunction(this.props.onMarkerCategoriesChanged)) {
this.props.onMarkerCategoriesChanged(markerCategories);
}
this.setAllCatagoriesDictionary();
this.setState({
markerCategories: markerCategories
});
}}
onMarkerChanged={(markerItem: IMarker, isNewMarker: boolean) => {
if(isNewMarker) {
this.state.markerItems.push(markerItem);
}
else {
const markerIndex: number = this.state.markerItems.IndexOf(m => m.id == markerItem.id);
if(markerIndex>=0) {
this.state.markerItems[markerIndex] = markerItem;
}
}
this.state.rightMouseTarget = undefined;
if(isFunction(this.props.onMarkerCollectionChanged)) {
this.props.onMarkerCollectionChanged(this.state.markerItems);
}
this.onConfigPanelDismiss();
}}>
Save
</PrimaryButton>
<DefaultButton onClick={() => { this.onConfigPanelDismiss(); }}>Cancel</DefaultButton>
</div>);
}}
// Stretch panel content to fill the available height so the footer is positioned
// at the bottom of the page
isFooterAtBottom={true}
>
<Dropdown
placeholder="Select a category"
label="Category"
defaultSelectedKey={this.state.currentMarker.categoryId}
onChange={(ev: any, option: IDropdownOption) => {
this.state.currentMarker.categoryId = option.key.toString();
this.setState({
currentMarker: this.state.currentMarker
});
}}
options={categoryOptions}
/>
<ChoiceGroup
label="Type of marker (on click)"
defaultSelectedKey={this.state.currentMarker.type}
onChange={(ev: any, option: IChoiceGroupOption) => {
this.state.currentMarker.type = option.key.toString() as MarkerType;
if( this.state.currentMarker.type == "Dialog") {
this.state.currentMarker.markerClickProps = {
headerText: "",
content: "",
url: ""
};
}
if( this.state.currentMarker.type == "Panel") {
this.state.currentMarker.markerClickProps = {
headerText: "",
content: ""
};
}
if( this.state.currentMarker.type == "None") {
this.state.currentMarker.markerClickProps = undefined;
}
if( this.state.currentMarker.type == "Url") {
this.state.currentMarker.markerClickProps = { url: ""};
}
this.setState({
currentMarker: this.state.currentMarker
});
}}
options={markerTypeOptions} />
{this.state.currentMarker.categoryId == Guid.empty.toString() &&
<>
<InlineColorPicker
label='Marker Color'
alphaType='none'
color={getColorFromString(this.state.currentMarker.iconProperties.markerColor)}
onChange={(ev: any, color: IColor) => {
this.state.currentMarker.iconProperties.markerColor = "#" + color.hex;
this.setState({
currentMarker: this.state.currentMarker
});
}}
/>
<TextField label='Icon' description='leaf blank for none' defaultValue={this.state.currentMarker.iconProperties.iconName} onChange={(ev: any, iconName: string) => {
this.state.currentMarker.iconProperties.iconName = iconName;
this.setState({
currentMarker: this.state.currentMarker
});
}} />
{!isNullOrEmpty(this.state.currentMarker.iconProperties.iconName) &&
<InlineColorPicker
label='Icon Color'
alphaType='none'
color={getColorFromString(this.state.currentMarker.iconProperties.iconColor)}
onChange={(ev: any, color: IColor) => {
this.state.currentMarker.iconProperties.iconColor = "#" + color.hex;
this.setState({
currentMarker: this.state.currentMarker
});
}}
/> }
<TextField label='Popup Text' description='leaf blank for none' defaultValue={this.state.currentMarker.popuptext} onChange={(ev: any, popuptext: string) => {
this.state.currentMarker.popuptext = popuptext;
this.setState({
currentMarker: this.state.currentMarker
});
}} />
</>
}
{this.state.currentMarker.type == "Url" &&
<>
<TextField label='Url' type='url' defaultValue={(this.state.currentMarker.markerClickProps as MarkerTypeUrl).url} onChange={(ev: any, url: string) => {
this.state.currentMarker.markerClickProps = { url: url };
this.setState({
currentMarker: this.state.currentMarker
});
}} />
</>
}
{this.state.currentMarker.type == "Panel" &&
<>
<TextField label='Panel Header' defaultValue={(this.state.currentMarker.markerClickProps as MarkerTypePanel).headerText} onChange={(ev: any, headerText: string) => {
(this.state.currentMarker.markerClickProps as MarkerTypePanel).headerText = headerText;
this.setState({
currentMarker: this.state.currentMarker
});
}} />
<TextField label='Panel Content' multiline defaultValue={(this.state.currentMarker.markerClickProps as MarkerTypePanel).content} onChange={(ev: any, content: string) => {
(this.state.currentMarker.markerClickProps as MarkerTypePanel).content = content;
this.setState({
currentMarker: this.state.currentMarker
});
}} />
</>
}
{this.state.currentMarker.type == "Dialog" &&
<>
<TextField label='Dialog Title' defaultValue={(this.state.currentMarker.markerClickProps as MarkerTypeDialog).headerText} onChange={(ev: any, headerText: string) => {
(this.state.currentMarker.markerClickProps as MarkerTypeDialog).headerText = headerText;
this.setState({
currentMarker: this.state.currentMarker
});
}} />
<TextField label='Dialog Content' multiline defaultValue={(this.state.currentMarker.markerClickProps as MarkerTypeDialog).content} onChange={(ev: any, content: string) => {
(this.state.currentMarker.markerClickProps as MarkerTypeDialog).content = content;
this.setState({
currentMarker: this.state.currentMarker
});
}} />
</>
}
</Panel>
);
}
@ -366,7 +302,7 @@ height: "400px"
});
}
private onContentPanelDismiss(): void {
private onContentPanelOrDialogDismiss(): void {
this.setState({
showClickContent: false,
currentMarker: null
@ -393,15 +329,17 @@ height: "400px"
const iconProperties: IMarkerIcon = isNullOrEmpty(markerCategory) ? marker.iconProperties : markerCategory.iconProperties;
wrapper.innerHTML = `<span>
<svg height="36px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="${iconProperties.markerColor}">
<!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<path d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"/>
</svg>
<span class="map-icon" style="color: ${iconProperties.iconColor}"></span>
</span>`;
// wrapper.innerHTML = `<span>
// <svg height="36px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="${iconProperties.markerColor}">
// <!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
// <path d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"/>
// </svg>
// <span class="map-icon" style="color: ${iconProperties.iconColor}"></span>
// </span>`;
ReactDom.render(<Icon iconName={iconProperties.iconName} /> , wrapper.querySelector(".map-icon"));
// ReactDom.render(<Icon iconName={iconProperties.iconName} /> , wrapper.querySelector(".map-icon"));
ReactDom.render(<MarkerIcon {...iconProperties} />, wrapper);
return wrapper;
};
@ -410,34 +348,28 @@ height: "400px"
}
private onCreateNewMarkerContextMenuItemClick(): void {
this.state.currentMarker = {
id: Guid.empty.toString(),
latitude: this.lastLatLngRightClickPosition.lat,
longitude: this.lastLatLngRightClickPosition.lng,
type: "Panel",
markerClickProps: {
headerText: "",
content: ""
},
categoryId: Guid.empty.toString(),
iconProperties: {
markerColor: "#" + randomString(6, 'abcdef0123456789'),
iconName: "",
iconColor: "#000"
},
popuptext: null
};
console.log('New clicked', this.lastLatLngRightClickPosition);
this.state.currentMarker = clone(emptyMarkerItem);
this.state.currentMarker.latitude = this.lastLatLngRightClickPosition.lat;
this.state.currentMarker.longitude = this.lastLatLngRightClickPosition.lng;
this.state.showAddOrEditMarkerPanel = true;
this.setState({...this.state})
}
private onCreateNewMarkerClick(marker: IMarker): void {
this.state.markerItems.push(marker);
this.state.rightMouseTarget = undefined;
private onSetStartView(): void {
console.log("SSC", this.map.getZoom(), this.map.getCenter())
if(isFunction(this.props.onStartViewSet)) {
const zoom: number = this.map.getZoom();
const latLng: L.LatLng = this.map.getCenter();
this.props.onStartViewSet(zoom, latLng.lat, latLng.lng);
}
}
private setAllCatagoriesDictionary(): void {
this.allCatagories = {};
this.state.markerCategories.forEach((category: IMarkerCategory) => {
this.allCatagories[category.id] = category;
});
}
}

View File

@ -0,0 +1,21 @@
import { Icon } from 'office-ui-fabric-react';
import * as React from 'react';
import { IMarkerIcon } from './IMapProps';
export const MarkerIcon: React.FunctionComponent<IMarkerIcon> = (iconProperties): JSX.Element => {
const iconColor: React.CSSProperties = {
color: iconProperties.iconColor.slice()
};
return (
<span>
<svg height="36px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill={iconProperties.markerColor}>
{/* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) */}
<path d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"/>
</svg>
<span className="map-icon" style={iconColor}><Icon iconName={iconProperties.iconName} /></span>
</span>
);
}