Skip to content

Added MapPicker component with Google Maps provider#21

Open
metjuperry wants to merge 3 commits into
masterfrom
users/matej.samler/mappicker
Open

Added MapPicker component with Google Maps provider#21
metjuperry wants to merge 3 commits into
masterfrom
users/matej.samler/mappicker

Conversation

@metjuperry

Copy link
Copy Markdown
Member
  • Introduced MapPicker component.
  • Added GoogleMapsProvider for rendering Google Maps.

- Introduced MapPicker component.
- Added GoogleMapsProvider for rendering Google Maps.
@brYch97

brYch97 commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

Please provide a demo env with the control bound via the Virtual Dataset PCF

@metjuperry

Copy link
Copy Markdown
Member Author

@brYch97 The test is setup here. You should have access

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new Map component module that can render dataset-based pin locations via a pluggable map provider, with an initial Google Maps provider implementation using @vis.gl/react-google-maps.

Changes:

  • Added src/components/Map module (interfaces, translations, component, provider abstraction, and Google Maps provider + styles).
  • Exported the new Map module from src/components/index.ts.
  • Updated build config / dependencies to include @vis.gl/react-google-maps and mark it as a Rollup external.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/components/index.ts Re-exports the new Map module from the component barrel.
src/components/Map/translations.ts Adds Map-related translation keys.
src/components/Map/interfaces.ts Defines Map control parameters/outputs and translation typing.
src/components/Map/index.ts Map module barrel exports.
src/components/Map/components/Map.tsx Loads pin locations from a dataset and renders the selected map provider.
src/components/Map/components/Map.css Basic container sizing for the Map component.
src/components/Map/providers/IMapProvider.ts Introduces provider abstraction and location shape.
src/components/Map/providers/index.ts Provider barrel exports (including Google provider).
src/components/Map/providers/GoogleMaps/GoogleMapsProvider.tsx Implements a Google Maps provider using @vis.gl/react-google-maps.
src/components/Map/providers/GoogleMaps/GoogleMapsProvider.css Adds fixed-size container styling for Google Maps rendering.
src/components/Grid/interfaces.ts Minor whitespace-only change.
rollup.config.js Adds @vis.gl/react-google-maps to Rollup externals list.
package.json Adds @vis.gl/react-google-maps (currently under devDependencies).
package-lock.json Lockfile updates reflecting the new dependency tree.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +1 to +4
import { APIProvider, Map, AdvancedMarker, Pin, Marker } from '@vis.gl/react-google-maps';
import { useMemo } from 'react';
import { IMapProvider, IMapProviderProps } from '../IMapProvider';
import './GoogleMapsProvider.css';

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AdvancedMarker and Pin are imported but unused. Also, there’s a comment about needing a mapId for AdvancedMarker even though the implementation uses Marker. Please remove unused imports and align/remove the comment so it matches the actual marker implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +42
dataset?.refresh();
}, []);

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

locations is only populated when onNewDataLoaded fires, so on first render the map will be empty (and may remain centered at (0,0)) even if the dataset already has records. Consider calling loadLocations() once on mount (and when pinMetadata changes) to render existing records immediately, and then keep the event listener for subsequent refreshes.

Suggested change
dataset?.refresh();
}, []);
loadLocations();
dataset?.refresh();
}, [dataset, loadLocations]);

Copilot uses AI. Check for mistakes.
import { IMapProvider } from "./providers";


export interface IMap extends IControl<IMapParameters, IMapOutputs, Partial<ITranslations<typeof mapTranslations>>, any> {

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last generic type argument to IControl is set to any here, which removes type-safety for the component props. If this control doesn’t accept extra component props, prefer a safer type like {}/unknown (or a specific props interface) to preserve strict typing.

Suggested change
export interface IMap extends IControl<IMapParameters, IMapOutputs, Partial<ITranslations<typeof mapTranslations>>, any> {
export interface IMap extends IControl<IMapParameters, IMapOutputs, Partial<ITranslations<typeof mapTranslations>>, unknown> {

Copilot uses AI. Check for mistakes.
Comment thread package.json
Comment on lines 36 to 39
"@types/validator": "^13.11.9",
"@types/xrm": "^9.0.81",
"@vis.gl/react-google-maps": "^1.7.1",
"babel-loader": "^8.2.3",

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vis.gl/react-google-maps is imported by the new GoogleMapsProvider at runtime, but it’s currently listed under devDependencies. Since it’s also marked as external in rollup.config.js, consumers of this library won’t automatically install it and will hit a runtime module resolution error. Move it to dependencies (or peerDependencies if you intentionally want the host app to supply it).

Copilot uses AI. Check for mistakes.
return (
<APIProvider apiKey={props.apiKey}>
<div className='google-maps-container'>
<Map defaultCenter={center} defaultZoom={13} disableDefaultUI style={{ width: '100%', height: '400px' }}>

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map center is computed from locations, but it’s passed via defaultCenter. defaultCenter is only used on initial mount, so when locations arrive after the initial render the map can remain centered at (0,0) (as your comment notes). Use a controlled center prop (or force a remount when center changes) so the map recenters when the locations state updates.

Suggested change
<Map defaultCenter={center} defaultZoom={13} disableDefaultUI style={{ width: '100%', height: '400px' }}>
<Map center={center} defaultZoom={13} disableDefaultUI style={{ width: '100%', height: '400px' }}>

Copilot uses AI. Check for mistakes.
setLocations(result);
}, [dataset, pinMetadata]);

useEffect(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be here - data load is called within DatasetControl which should always be used when rendering Dataset Base Control.

useEventEmitter<IDataProviderEventListeners>(dataset, 'onNewDataLoaded', loadLocations);

return (
<div className='map-picker'>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use mergeStyleSets to generate unique class names

@@ -0,0 +1,4 @@
.google-maps-container {
width: 100%;
height: 400px;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The control should ideally be responsive - eg. when it's full page, it should expand to the entire page and etc. Adapters automatically provide the necessary styles for the container, but you still need to include support for responsivity in the base control itself (see Grid for reference).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also keep all styling withing mergeStyleSets

EnableAutoSave?: Omit<ITwoOptionsProperty, 'attributes'>;
DefaultExpandedGroupLevel?: Omit<IWholeNumberProperty, 'attributes'>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be reverted

@StepanHoudek

StepanHoudek commented Jun 11, 2026

Copy link
Copy Markdown

We need pin popup support. Example:
image

Discussed with BRY, here are his ideas:
tady by bylo fajn se tapnout primo do toho popupu, co jsou ti schopne vyrobit google maps po kliknuti

vim, ze matej mel namyslene a i jsme se na tom schodli, ze by mela pro ty google maps existovat nejaka extensibilita pro to, jakym zpusobem vyrenderujes ty piny

soucasti takove extensibility by idealne melo byt i tohle

nevim jak daleko se s tou extensibilitou dostal, ale melo by to fungovat v zakladu tak, ze ta kontrolka acceptuje descriptor, ktery na sobe ma ruzne funkce ktere se optionally mohou zavolat, aby se overridenul nejaky default

import { APIProvider, Map, Marker, InfoWindow } from '@vis.gl/react-google-maps';
import { useMemo, useState } from 'react';
import { IMapProvider, IMapProviderProps, IMapLocation } from '../IMapProvider';
import './GoogleMapsProvider.css';

export interface IGoogleMapsConfig {
    apiKey: string;
    onRenderPinPopup?: (location: IMapLocation) => React.ReactNode;
}

const GoogleMapsMap = (props: IMapProviderProps & { apiKey: string; onRenderPinPopup?: IGoogleMapsConfig['onRenderPinPopup'] }) => {
    const [selectedLocation, setSelectedLocation] = useState<IMapLocation | null>(null);

    const center = useMemo(() => {
        if (props.locations.length === 0) return { lat: 0, lng: 0 };
        const lat = props.locations.reduce((s, l) => s + l.latitude, 0) / props.locations.length;
        const lng = props.locations.reduce((s, l) => s + l.longitude, 0) / props.locations.length;
        return { lat, lng };
    }, [props.locations]);

    return (
        <APIProvider apiKey={props.apiKey}>
            <div className='google-maps-container'>
                <Map defaultCenter={center} defaultZoom={13} disableDefaultUI style={{ width: '100%', height: '400px' }}>
                    {props.locations.map((location) => (
                        <Marker
                            key={location.id}
                            position={{ lat: location.latitude, lng: location.longitude }}
                            onClick={() => setSelectedLocation(location)}
                        />
                    ))}
                    {selectedLocation && props.onRenderPinPopup && (
                        <InfoWindow
                            position={{ lat: selectedLocation.latitude, lng: selectedLocation.longitude }}
                            onCloseClick={() => setSelectedLocation(null)}
                        >
                            {props.onRenderPinPopup(selectedLocation)}
                        </InfoWindow>
                    )}
                </Map>
            </div>
        </APIProvider>
    );
};

export const createGoogleMapsProvider = (config: IGoogleMapsConfig): IMapProvider => {
    return (props: IMapProviderProps) => (
        <GoogleMapsMap {...props} apiKey={config.apiKey} onRenderPinPopup={config.onRenderPinPopup} />
    );
};

const mapProvider = createGoogleMapsProvider({
    apiKey: 'YOUR_API_KEY',
    onRenderPinPopup: (location) => (
        <PinPopup
            recordId={location.id}
            linkedRecords={linkedRecordsMap[location.id]}
        />
    )
});

tohle je AI slop co sem rychle vygeneroval, ale asi nejak takhle bych si tu extensibilitu predstavoval

do toho descriptoru/configu se pak daji pridat ruzne dalsi parametry typu feature flags atd.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants