catagories management and UI Updates
This commit is contained in:
parent
64b7f9ebf1
commit
6dbad8548f
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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",
|
||||
|
|
|
@ -13,4 +13,9 @@
|
|||
height: 14px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: darkgrey;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,75 +63,48 @@ 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) => {
|
||||
map.on("contextmenu", (ev: L.LeafletEvent) => {
|
||||
<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;
|
||||
this.lastLatLngRightClickPosition = (ev as any).latlng;
|
||||
|
||||
this.setState({
|
||||
rightMouseTarget: {x: ((ev as any).originalEvent as MouseEvent).clientX, y: ((ev as any).originalEvent as MouseEvent).clientY }
|
||||
});
|
||||
this.setState({
|
||||
rightMouseTarget: {x: ((ev as any).originalEvent as MouseEvent).clientX, y: ((ev as any).originalEvent as MouseEvent).clientY }
|
||||
});
|
||||
|
||||
});
|
||||
this.map = map;
|
||||
|
||||
}} style={{
|
||||
height: "400px"
|
||||
}}>
|
||||
});
|
||||
this.map = map;
|
||||
}
|
||||
}
|
||||
style={{height: "400px"}}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
|
||||
{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;
|
||||
const popupText: string = !useCategory ? marker.popuptext : isNullOrEmpty(markerCategory.popuptext) ? markerCategory.name : markerCategory.popuptext;
|
||||
|
||||
return (
|
||||
<Marker position={[marker.latitude, marker.longitude]} key={`marker_${index}`} icon={this.createIcon(marker, markerCategory)} eventHandlers={
|
||||
{
|
||||
|
||||
click: (ev: L.LeafletMouseEvent) => {
|
||||
|
||||
let showEditPanel: boolean = this.props.isEditMode;
|
||||
|
||||
this.setState({
|
||||
currentMarker: marker,
|
||||
showClickContent: !showEditPanel,
|
||||
showAddOrEditMarkerPanel: showEditPanel
|
||||
});
|
||||
},
|
||||
mouseover: (ev: L.LeafletMouseEvent) => {
|
||||
(ev.target as any).openPopup();
|
||||
},
|
||||
mouseout: (ev: L.LeafletMouseEvent) => {
|
||||
(ev.target as any).closePopup();
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
{!isNullOrEmpty(popupText) &&
|
||||
<Popup>
|
||||
{popupText}
|
||||
</Popup>
|
||||
}
|
||||
</Marker>
|
||||
);
|
||||
})}
|
||||
{this.renderMarker()}
|
||||
</MapContainer>
|
||||
|
||||
{this.renderLegend()}
|
||||
|
||||
<ContextualMenu
|
||||
items={this.menuItems}
|
||||
|
@ -134,36 +121,129 @@ height: "400px"
|
|||
/>
|
||||
{this.showAddOrEditMarkerPanel()}
|
||||
{this.showClickContent()}
|
||||
</MapContainer>
|
||||
</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;
|
||||
const popupText: string = !useCategory ? marker.popuptext : isNullOrEmpty(markerCategory.popuptext) ? markerCategory.name : markerCategory.popuptext;
|
||||
|
||||
return (
|
||||
<Marker position={[marker.latitude, marker.longitude]} key={`marker_${index}`} icon={this.createIcon(marker, markerCategory)} eventHandlers={
|
||||
{
|
||||
|
||||
click: (ev: L.LeafletMouseEvent) => {
|
||||
|
||||
let showEditPanel: boolean = this.props.isEditMode;
|
||||
|
||||
this.setState({
|
||||
currentMarker: marker,
|
||||
showClickContent: !showEditPanel,
|
||||
showAddOrEditMarkerPanel: showEditPanel
|
||||
});
|
||||
},
|
||||
mouseover: (ev: L.LeafletMouseEvent) => {
|
||||
(ev.target as any).openPopup();
|
||||
},
|
||||
mouseout: (ev: L.LeafletMouseEvent) => {
|
||||
(ev.target as any).closePopup();
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
{!isNullOrEmpty(popupText) &&
|
||||
<Popup>
|
||||
{popupText}
|
||||
</Popup>
|
||||
}
|
||||
</Marker>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 == "Panel") {
|
||||
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;
|
||||
this.onConfigPanelDismiss();
|
||||
}}>
|
||||
Save
|
||||
</PrimaryButton>
|
||||
<DefaultButton onClick={() => { this.onConfigPanelDismiss(); }}>Cancel</DefaultButton>
|
||||
</div>);
|
||||
<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
|
||||
});
|
||||
}}
|
||||
// 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
|
||||
});
|
||||
onMarkerChanged={(markerItem: IMarker, isNewMarker: boolean) => {
|
||||
|
||||
}}
|
||||
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(isNewMarker) {
|
||||
this.state.markerItems.push(markerItem);
|
||||
}
|
||||
else {
|
||||
const markerIndex: number = this.state.markerItems.IndexOf(m => m.id == markerItem.id);
|
||||
|
||||
if( this.state.currentMarker.type == "Dialog") {
|
||||
this.state.currentMarker.markerClickProps = {
|
||||
headerText: "",
|
||||
content: "",
|
||||
url: ""
|
||||
};
|
||||
if(markerIndex>=0) {
|
||||
this.state.markerItems[markerIndex] = markerItem;
|
||||
}
|
||||
|
||||
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.rightMouseTarget = undefined;
|
||||
|
||||
{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
|
||||
});
|
||||
}} />
|
||||
</>
|
||||
if(isFunction(this.props.onMarkerCollectionChanged)) {
|
||||
this.props.onMarkerCollectionChanged(this.state.markerItems);
|
||||
}
|
||||
|
||||
{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>
|
||||
this.onConfigPanelDismiss();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue