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 install -D @types/leaflet babel-loader @babel/core @babel/preset-env @babel/plugin-proposal-nullish-coalescing-operator
|
||||||
npm run serve
|
npm run serve
|
||||||
exit
|
exit
|
||||||
|
npm run serve
|
||||||
|
npm install @pnp/spfx-controls-react --save --save-exact
|
||||||
|
npm run serve
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
},
|
},
|
||||||
"externals": {},
|
"externals": {},
|
||||||
"localizedResources": {
|
"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-office-ui-fabric-core": "1.14.0",
|
||||||
"@microsoft/sp-property-pane": "1.14.0",
|
"@microsoft/sp-property-pane": "1.14.0",
|
||||||
"@microsoft/sp-webpart-base": "1.14.0",
|
"@microsoft/sp-webpart-base": "1.14.0",
|
||||||
|
"@pnp/spfx-controls-react": "3.6.0",
|
||||||
"@spfxappdev/utility": "^1.1.0",
|
"@spfxappdev/utility": "^1.1.0",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"office-ui-fabric-react": "7.174.1",
|
"office-ui-fabric-react": "7.174.1",
|
||||||
|
|
|
@ -13,4 +13,9 @@
|
||||||
height: 14px;
|
height: 14px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background: darkgrey;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import { isset, isNullOrEmpty } from '@spfxappdev/utility';
|
||||||
|
|
||||||
export interface IInlineColorPickerProps extends IColorPickerProps {
|
export interface IInlineColorPickerProps extends IColorPickerProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
isDisbaled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IInlineColorPickerState {
|
interface IInlineColorPickerState {
|
||||||
|
@ -17,6 +18,11 @@ export class InlineColorPicker extends React.Component<IInlineColorPickerProps,
|
||||||
isPickerVisible: false,
|
isPickerVisible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static defaultProps: IInlineColorPickerProps = {
|
||||||
|
color: '#000000',
|
||||||
|
isDisbaled: false
|
||||||
|
}
|
||||||
|
|
||||||
private targetElement: HTMLDivElement = null;
|
private targetElement: HTMLDivElement = null;
|
||||||
|
|
||||||
public render(): React.ReactElement<IInlineColorPickerProps> {
|
public render(): React.ReactElement<IInlineColorPickerProps> {
|
||||||
|
@ -41,13 +47,18 @@ export class InlineColorPicker extends React.Component<IInlineColorPickerProps,
|
||||||
<Label>{this.props.label}</Label>
|
<Label>{this.props.label}</Label>
|
||||||
}
|
}
|
||||||
<div
|
<div
|
||||||
className={styles['inline-color-picker']}
|
className={styles['inline-color-picker'] + ` ${this.props.isDisbaled?styles['disabled']:''}`}
|
||||||
ref={(r) => {
|
ref={(r) => {
|
||||||
if(isset(r)) {
|
if(isset(r)) {
|
||||||
this.targetElement = r;
|
this.targetElement = r;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
||||||
|
if(this.props.isDisbaled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ isPickerVisible: true });
|
this.setState({ isPickerVisible: true });
|
||||||
}}>
|
}}>
|
||||||
<div className={styles['inline-color-picker-inner']} style={customCss}></div>
|
<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.
|
// 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
|
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||||
"requiresCustomScript": false,
|
"requiresCustomScript": false,
|
||||||
|
"supportsFullBleed": true,
|
||||||
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
|
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
|
||||||
"supportsThemeVariants": true,
|
"supportsThemeVariants": true,
|
||||||
|
|
||||||
"preconfiguredEntries": [{
|
"preconfiguredEntries": [{
|
||||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
"groupId": "142aa22c-9004-41c2-9821-2d78eed048d1", // SPFx App Dev
|
||||||
"group": { "default": "Other" },
|
"group": { "default": "Other" },
|
||||||
"title": { "default": "Map" },
|
"title": { "default": "Map" },
|
||||||
"description": { "default": "Map description" },
|
"description": { "default": "Map description" },
|
||||||
"officeFabricIconFontName": "Page",
|
"officeFabricIconFontName": "MapPin",
|
||||||
"properties": {
|
"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 { DisplayMode, Version } from '@microsoft/sp-core-library';
|
||||||
import {
|
import {
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
PropertyPaneTextField
|
PropertyPaneTextField,
|
||||||
|
PropertyPaneToggle,
|
||||||
|
PropertyPaneSlider,
|
||||||
|
PropertyPaneButton,
|
||||||
|
PropertyPaneLabel
|
||||||
|
|
||||||
} from '@microsoft/sp-property-pane';
|
} from '@microsoft/sp-property-pane';
|
||||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||||
|
@ -12,10 +17,23 @@ import * as strings from 'MapWebPartStrings';
|
||||||
import Map from './components/Map';
|
import Map from './components/Map';
|
||||||
import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps';
|
import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps';
|
||||||
|
|
||||||
|
export interface IMapPlugins {
|
||||||
|
searchBox: boolean;
|
||||||
|
markercluster: boolean;
|
||||||
|
legend: boolean;
|
||||||
|
zoomControl: boolean;
|
||||||
|
scrollWheelZoom: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IMapWebPartProps {
|
export interface IMapWebPartProps {
|
||||||
markerItems: IMarker[];
|
markerItems: IMarker[];
|
||||||
markerCategories: IMarkerCategory[];
|
markerCategories: IMarkerCategory[];
|
||||||
|
title: string;
|
||||||
|
center: [number, number];
|
||||||
|
startZoom: number;
|
||||||
|
maxZoom: number;
|
||||||
|
height: number;
|
||||||
|
plugins: IMapPlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> {
|
export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> {
|
||||||
|
@ -27,58 +45,50 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
this._environmentMessage = this._getEnvironmentMessage();
|
this._environmentMessage = this._getEnvironmentMessage();
|
||||||
|
|
||||||
return super.onInit();
|
return super.onInit();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
|
|
||||||
const dummyData: IMarker = {
|
console.log(this.properties);
|
||||||
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"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const element: React.ReactElement<IMapProps> = React.createElement(
|
const element: React.ReactElement<IMapProps> = React.createElement(
|
||||||
Map,
|
Map,
|
||||||
{
|
{
|
||||||
markerItems: this.properties.markerItems||[dummyData, dummyData2],
|
markerItems: this.properties.markerItems||[],
|
||||||
markerCategories: this.properties.markerCategories||[dummyCategory],
|
markerCategories: this.properties.markerCategories||[],
|
||||||
isEditMode: this.displayMode == DisplayMode.Edit
|
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);
|
ReactDom.render(element, this.domElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
|
||||||
|
// super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue)
|
||||||
|
// console.log()
|
||||||
|
// }
|
||||||
|
|
||||||
private _getEnvironmentMessage(): string {
|
private _getEnvironmentMessage(): string {
|
||||||
if (!!this.context.sdks.microsoftTeams) { // running in Teams
|
if (!!this.context.sdks.microsoftTeams) { // running in Teams
|
||||||
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
|
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
|
||||||
|
@ -121,8 +131,26 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
|
||||||
{
|
{
|
||||||
groupName: strings.BasicGroupName,
|
groupName: strings.BasicGroupName,
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneTextField('description', {
|
PropertyPaneToggle('plugins.searchBox', {
|
||||||
label: strings.DescriptionFieldLabel
|
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 type MarkerType = "Panel"|"Dialog"|"Url"|"None";
|
||||||
|
|
||||||
|
export interface IMarkerClickProps {
|
||||||
|
url: IMarkerUrlProperties;
|
||||||
|
content: IMarkerContentProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMarkerUrlProperties {
|
||||||
export type MarkerTypePanel = {
|
href: string;
|
||||||
headerText: string;
|
target: '_self'|'_blank'|'embedded';
|
||||||
content: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MarkerTypeUrl = {
|
export interface IMarkerContentProperties {
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MarkerTypeDialog = {
|
|
||||||
headerText: string;
|
headerText: string;
|
||||||
content?: string;
|
html: string;
|
||||||
url?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IMarkerIcon {
|
export interface IMarkerIcon {
|
||||||
|
@ -39,15 +40,40 @@ export interface IMarker {
|
||||||
categoryId: string;
|
categoryId: string;
|
||||||
iconProperties?: IMarkerIcon;
|
iconProperties?: IMarkerIcon;
|
||||||
popuptext?: string;
|
popuptext?: string;
|
||||||
markerClickProps?: MarkerTypePanel|MarkerTypeUrl|MarkerTypeDialog;
|
markerClickProps?: IMarkerClickProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapProps {
|
export interface IMapProps {
|
||||||
markerItems: IMarker[];
|
markerItems: IMarker[];
|
||||||
markerCategories: IMarkerCategory[];
|
markerCategories: IMarkerCategory[];
|
||||||
onMarkerCollectionChanged?(markerItems: IMarker[]);
|
|
||||||
onMarkerCategoriesChanged?(markerCategories: IMarkerCategory[]);
|
|
||||||
isEditMode: boolean;
|
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 '~office-ui-fabric-react/dist/sass/References.scss';
|
||||||
|
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||||
|
|
||||||
|
|
||||||
.map {
|
.map {
|
||||||
|
@ -14,5 +14,156 @@
|
||||||
left: 8px;
|
left: 8px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
color: #fff;
|
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 React from 'react';
|
||||||
import * as ReactDom from 'react-dom';
|
import * as ReactDom from 'react-dom';
|
||||||
import styles from './Map.module.scss';
|
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 { clone } from '@microsoft/sp-lodash-subset';
|
||||||
import { MapContainer, TileLayer, Marker, Popup, Tooltip } from 'react-leaflet';
|
import { MapContainer, TileLayer, Marker, Popup, Tooltip } from 'react-leaflet';
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import * as L from 'leaflet';
|
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 { 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 } from '@spfxappdev/utility';
|
import { randomString, isset, isNullOrEmpty, getDeepOrDefault, cssClasses } from '@spfxappdev/utility';
|
||||||
import '@spfxappdev/utility/lib/extensions/StringExtensions';
|
import '@spfxappdev/utility/lib/extensions/StringExtensions';
|
||||||
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
|
import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
|
||||||
import { Guid } from '@microsoft/sp-core-library';
|
import { DisplayMode, Guid } from '@microsoft/sp-core-library';
|
||||||
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker'
|
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker'
|
||||||
import { TextField } from '@microsoft/office-ui-fabric-react-bundle';
|
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 {
|
interface IMapState {
|
||||||
markerItems: IMarker[];
|
markerItems: IMarker[];
|
||||||
|
markerCategories: IMarkerCategory[];
|
||||||
rightMouseTarget?: any;
|
rightMouseTarget?: any;
|
||||||
showAddOrEditMarkerPanel: boolean;
|
showAddOrEditMarkerPanel: boolean;
|
||||||
currentMarker?: IMarker;
|
currentMarker?: IMarker;
|
||||||
|
@ -26,6 +32,7 @@ export default class Map extends React.Component<IMapProps, IMapState> {
|
||||||
|
|
||||||
public state: IMapState = {
|
public state: IMapState = {
|
||||||
markerItems: clone(this.props.markerItems),
|
markerItems: clone(this.props.markerItems),
|
||||||
|
markerCategories: clone(this.props.markerCategories),
|
||||||
showAddOrEditMarkerPanel: false,
|
showAddOrEditMarkerPanel: false,
|
||||||
showClickContent: false
|
showClickContent: false
|
||||||
};
|
};
|
||||||
|
@ -35,10 +42,17 @@ export default class Map extends React.Component<IMapProps, IMapState> {
|
||||||
private menuItems: IContextualMenuItem[] = [
|
private menuItems: IContextualMenuItem[] = [
|
||||||
{
|
{
|
||||||
key: 'newItem',
|
key: 'newItem',
|
||||||
text: 'New',
|
text: 'Add a new marker',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.onCreateNewMarkerContextMenuItemClick();
|
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) {
|
constructor(props: IMapProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.setAllCatagoriesDictionary();
|
||||||
props.markerCategories.forEach((category: IMarkerCategory) => {
|
|
||||||
this.allCatagories[category.id] = category;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactElement<IMapProps> {
|
public render(): React.ReactElement<IMapProps> {
|
||||||
|
|
||||||
|
//
|
||||||
return (
|
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
|
||||||
<MapContainer center={[49.318121, 10.624094]} zoom={13} maxZoom={500} whenCreated={(map: L.Map) => {
|
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) => {
|
map.on("contextmenu", (ev: L.LeafletEvent) => {
|
||||||
|
|
||||||
this.lastLatLngRightClickPosition = (ev as any).latlng;
|
this.lastLatLngRightClickPosition = (ev as any).latlng;
|
||||||
|
@ -73,15 +91,43 @@ export default class Map extends React.Component<IMapProps, IMapState> {
|
||||||
|
|
||||||
});
|
});
|
||||||
this.map = map;
|
this.map = map;
|
||||||
|
}
|
||||||
}} style={{
|
}
|
||||||
height: "400px"
|
style={{height: "400px"}}
|
||||||
}}>
|
>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
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 => {
|
{this.state.markerItems.map((marker: IMarker, index: number): JSX.Element => {
|
||||||
const useCategory: boolean = isset(this.allCatagories[marker.categoryId]);
|
const useCategory: boolean = isset(this.allCatagories[marker.categoryId]);
|
||||||
const markerCategory: IMarkerCategory = useCategory ? this.allCatagories[marker.categoryId] : null;
|
const markerCategory: IMarkerCategory = useCategory ? this.allCatagories[marker.categoryId] : null;
|
||||||
|
@ -118,52 +164,86 @@ height: "400px"
|
||||||
</Marker>
|
</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 {
|
private showClickContent(): JSX.Element {
|
||||||
if(!this.state.showClickContent || isNullOrEmpty(this.state.currentMarker)) {
|
if(!this.state.showClickContent || isNullOrEmpty(this.state.currentMarker)) {
|
||||||
return (<></>);
|
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") {
|
if (this.state.currentMarker.type == "Panel") {
|
||||||
return (<Panel
|
return (<Panel
|
||||||
type={PanelType.medium}
|
type={PanelType.medium}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
onDismiss={() => { this.onContentPanelDismiss() }}
|
onDismiss={() => { this.onContentPanelOrDialogDismiss() }}
|
||||||
headerText={(this.state.currentMarker.markerClickProps as MarkerTypePanel).headerText}
|
headerText={this.state.currentMarker.markerClickProps.content.headerText}
|
||||||
closeButtonAriaLabel="Close"
|
closeButtonAriaLabel="Close"
|
||||||
onRenderFooterContent={(props: IPanelProps) => {
|
onRenderFooterContent={(props: IPanelProps) => {
|
||||||
return (<div>
|
return (<div>
|
||||||
<DefaultButton onClick={() => { this.onContentPanelDismiss(); }}>Close</DefaultButton>
|
<DefaultButton onClick={() => { this.onContentPanelOrDialogDismiss(); }}>Close</DefaultButton>
|
||||||
</div>);
|
</div>);
|
||||||
}}
|
}}
|
||||||
// Stretch panel content to fill the available height so the footer is positioned
|
// Stretch panel content to fill the available height so the footer is positioned
|
||||||
// at the bottom of the page
|
// at the bottom of the page
|
||||||
isFooterAtBottom={true}
|
isFooterAtBottom={true}
|
||||||
>
|
>
|
||||||
{(this.state.currentMarker.markerClickProps as MarkerTypePanel).headerText}
|
<RichText isEditMode={false} value={this.state.currentMarker.markerClickProps.content.html} />
|
||||||
|
|
||||||
</Panel>);
|
</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 {
|
private showAddOrEditMarkerPanel(): JSX.Element {
|
||||||
|
@ -172,190 +252,46 @@ height: "400px"
|
||||||
return (<></>);
|
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 (
|
return (
|
||||||
<Panel
|
<AddOrEditPanel
|
||||||
type={PanelType.medium}
|
markerCategories={this.state.markerCategories}
|
||||||
isOpen={this.state.showAddOrEditMarkerPanel}
|
markerItem={this.state.currentMarker}
|
||||||
onDismiss={() => { this.onConfigPanelDismiss() }}
|
onDismiss={() => { this.onConfigPanelDismiss(); }}
|
||||||
headerText={headerText}
|
onMarkerCategoriesChanged={(markerCategories: IMarkerCategory[]) => {
|
||||||
closeButtonAriaLabel="Close"
|
this.state.markerCategories = markerCategories;
|
||||||
onRenderFooterContent={(props: IPanelProps) => {
|
|
||||||
return (<div>
|
if(isFunction(this.props.onMarkerCategoriesChanged)) {
|
||||||
<PrimaryButton onClick={() => {
|
this.props.onMarkerCategoriesChanged(markerCategories);
|
||||||
this.state.currentMarker.id = Guid.newGuid().toString();
|
}
|
||||||
// this.onCreateNewMarkerClick(clone(this.state.currentMarker));
|
|
||||||
this.onCreateNewMarkerClick(this.state.currentMarker);
|
this.setAllCatagoriesDictionary();
|
||||||
// this.state.currentMarker = null;
|
|
||||||
|
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();
|
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({
|
this.setState({
|
||||||
showClickContent: false,
|
showClickContent: false,
|
||||||
currentMarker: null
|
currentMarker: null
|
||||||
|
@ -393,15 +329,17 @@ height: "400px"
|
||||||
|
|
||||||
const iconProperties: IMarkerIcon = isNullOrEmpty(markerCategory) ? marker.iconProperties : markerCategory.iconProperties;
|
const iconProperties: IMarkerIcon = isNullOrEmpty(markerCategory) ? marker.iconProperties : markerCategory.iconProperties;
|
||||||
|
|
||||||
wrapper.innerHTML = `<span>
|
// wrapper.innerHTML = `<span>
|
||||||
<svg height="36px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="${iconProperties.markerColor}">
|
// <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) -->
|
// <!-- 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"/>
|
// <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>
|
// </svg>
|
||||||
<span class="map-icon" style="color: ${iconProperties.iconColor}"></span>
|
// <span class="map-icon" style="color: ${iconProperties.iconColor}"></span>
|
||||||
</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;
|
return wrapper;
|
||||||
};
|
};
|
||||||
|
@ -410,34 +348,28 @@ height: "400px"
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCreateNewMarkerContextMenuItemClick(): void {
|
private onCreateNewMarkerContextMenuItemClick(): void {
|
||||||
|
this.state.currentMarker = clone(emptyMarkerItem);
|
||||||
|
this.state.currentMarker.latitude = this.lastLatLngRightClickPosition.lat;
|
||||||
this.state.currentMarker = {
|
this.state.currentMarker.longitude = this.lastLatLngRightClickPosition.lng;
|
||||||
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.showAddOrEditMarkerPanel = true;
|
this.state.showAddOrEditMarkerPanel = true;
|
||||||
|
|
||||||
this.setState({...this.state})
|
this.setState({...this.state})
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCreateNewMarkerClick(marker: IMarker): void {
|
private onSetStartView(): void {
|
||||||
this.state.markerItems.push(marker);
|
console.log("SSC", this.map.getZoom(), this.map.getCenter())
|
||||||
this.state.rightMouseTarget = undefined;
|
|
||||||
|
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