[Map] Create Map component

This commit is contained in:
Hugo Alliaume
2024-06-10 23:06:54 +02:00
committed by Javier Eguiluz
commit 12cb4b2dd1
33 changed files with 1610 additions and 0 deletions

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
/Tests export-ignore
/phpunit.xml.dist export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

5
CHANGELOG.md Normal file
View File

@@ -0,0 +1,5 @@
# CHANGELOG
## Unreleased
- Bridge added

19
LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2024-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

87
README.md Normal file
View File

@@ -0,0 +1,87 @@
# Symfony UX Map: Google Maps
[Google Maps](https://developers.google.com/maps/documentation/javascript/overview) integration for Symfony UX Map.
## DSN example
```dotenv
UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default
# With options
UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?version=weekly
UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?language=fr&region=FR
```
Available options:
| Option | Description | Default |
|------------|------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|
| `id` | The id of the script tag | `__googleMapsScriptId` |
| `language` | Force language, see [list of supported languages](https://developers.google.com/maps/faq#languagesupport) specified in the browser | The user's preferred language |
| `region` | Unicode region subtag identifiers compatible with [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) | |
| `nonce` | Use a cryptographic nonce attribute | |
| `retries` | The number of script load retries | 3 |
| `url` | Custom url to load the Google Maps API script | `https://maps.googleapis.com/maps/api/js` |
| `version` | The release channels or version numbers | `weekly` |
## Map options
You can use the `GoogleOptions` class to configure your `Map`::
```php
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
use Symfony\UX\Map\Bridge\Google\Option\FullscreenControlOptions;
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlOptions;
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
use Symfony\UX\Map\Bridge\Google\Option\StreetViewControlOptions;
use Symfony\UX\Map\Bridge\Google\Option\ZoomControlOptions;
use Symfony\UX\Map\Point;
use Symfony\UX\Map\Map;
$map = (new Map())
->center(new Point(48.8566, 2.3522))
->zoom(6);
// To configure controls options, and some other options:
$googleOptions = (new GoogleOptions())
->mapId('YOUR_MAP_ID')
->gestureHandling(GestureHandling::GREEDY)
->backgroundColor('#f00')
->doubleClickZoom(true)
->zoomControlOptions(new ZoomControlOptions(
position: ControlPosition::BLOCK_START_INLINE_END,
))
->mapTypeControlOptions(new MapTypeControlOptions(
mapTypeIds: ['roadmap'],
position: ControlPosition::INLINE_END_BLOCK_START,
style: MapTypeControlStyle::DROPDOWN_MENU,
))
->streetViewControlOptions(new StreetViewControlOptions(
position: ControlPosition::BLOCK_END_INLINE_START,
))
->fullscreenControlOptions(new FullscreenControlOptions(
position: ControlPosition::INLINE_START_BLOCK_END,
))
;
// To disable controls:
$googleOptions = (new GoogleOptions())
->mapId('YOUR_MAP_ID')
->zoomControl(false)
->mapTypeControl(false)
->streetViewControl(false)
->fullscreenControl(false)
;
// Add the custom options to the map
$map->options($googleOptions);
```
## Resources
- [Documentation](https://symfony.com/bundles/ux-map/current/index.html)
- [Report issues](https://github.com/symfony/ux/issues) and
[send Pull Requests](https://github.com/symfony/ux/pulls)
in the [main Symfony UX repository](https://github.com/symfony/ux)

26
assets/dist/map_controller.d.ts vendored Normal file
View File

@@ -0,0 +1,26 @@
/// <reference types="google.maps" />
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller';
import type { LoaderOptions } from '@googlemaps/js-api-loader';
type MapOptions = Pick<google.maps.MapOptions, 'mapId' | 'gestureHandling' | 'backgroundColor' | 'disableDoubleClickZoom' | 'zoomControl' | 'zoomControlOptions' | 'mapTypeControl' | 'mapTypeControlOptions' | 'streetViewControl' | 'streetViewControlOptions' | 'fullscreenControl' | 'fullscreenControlOptions'>;
export default class extends AbstractMapController<MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindow> {
static values: {
providerOptions: ObjectConstructor;
};
providerOptionsValue: Pick<LoaderOptions, 'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version'>;
connect(): Promise<void>;
protected doCreateMap({ center, zoom, options, }: {
center: Point;
zoom: number;
options: MapOptions;
}): google.maps.Map;
protected doCreateMarker(definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>): google.maps.marker.AdvancedMarkerElement;
protected doCreateInfoWindow({ definition, marker, }: {
definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>['infoWindow'];
marker: google.maps.marker.AdvancedMarkerElement;
}): google.maps.InfoWindow;
private createTextOrElement;
private closeInfoWindowsExcept;
protected doFitBoundsToMarkers(): void;
}
export {};

103
assets/dist/map_controller.js vendored Normal file
View File

@@ -0,0 +1,103 @@
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
import { Loader } from '@googlemaps/js-api-loader';
let loader;
let library;
class default_1 extends AbstractMapController {
async connect() {
if (!loader) {
loader = new Loader(this.providerOptionsValue);
}
const { Map: _Map, InfoWindow } = await loader.importLibrary('maps');
const { AdvancedMarkerElement } = await loader.importLibrary('marker');
library = { _Map, AdvancedMarkerElement, InfoWindow };
super.connect();
}
doCreateMap({ center, zoom, options, }) {
options.zoomControl = typeof options.zoomControlOptions !== 'undefined';
options.mapTypeControl = typeof options.mapTypeControlOptions !== 'undefined';
options.streetViewControl = typeof options.streetViewControlOptions !== 'undefined';
options.fullscreenControl = typeof options.fullscreenControlOptions !== 'undefined';
return new library._Map(this.element, {
...options,
center,
zoom,
});
}
doCreateMarker(definition) {
const { position, title, infoWindow, rawOptions = {}, ...otherOptions } = definition;
const marker = new library.AdvancedMarkerElement({
position,
title,
...otherOptions,
...rawOptions,
map: this.map,
});
if (infoWindow) {
this.createInfoWindow({ definition: infoWindow, marker });
}
return marker;
}
doCreateInfoWindow({ definition, marker, }) {
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
const infoWindow = new library.InfoWindow({
headerContent: this.createTextOrElement(headerContent),
content: this.createTextOrElement(content),
...otherOptions,
...rawOptions,
});
if (definition.opened) {
infoWindow.open({
map: this.map,
shouldFocus: false,
anchor: marker,
});
}
marker.addListener('click', () => {
if (definition.autoClose) {
this.closeInfoWindowsExcept(infoWindow);
}
infoWindow.open({
map: this.map,
anchor: marker,
});
});
return infoWindow;
}
createTextOrElement(content) {
if (!content) {
return null;
}
if (content.includes('<')) {
const div = document.createElement('div');
div.innerHTML = content;
return div;
}
return content;
}
closeInfoWindowsExcept(infoWindow) {
this.infoWindows.forEach((otherInfoWindow) => {
if (otherInfoWindow !== infoWindow) {
otherInfoWindow.close();
}
});
}
doFitBoundsToMarkers() {
if (this.markers.length === 0) {
return;
}
const bounds = new google.maps.LatLngBounds();
this.markers.forEach((marker) => {
if (!marker.position) {
return;
}
bounds.extend(marker.position);
});
this.map.fitBounds(bounds);
}
}
default_1.values = {
providerOptions: Object,
};
export { default_1 as default };

39
assets/package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "@symfony/ux-map-google",
"description": "GoogleMaps bridge for Symfony UX Map, integrate interactive maps in your Symfony applications",
"license": "MIT",
"version": "1.0.0",
"type": "module",
"main": "dist/map_controller.js",
"types": "dist/map_controller.d.ts",
"symfony": {
"controllers": {
"map": {
"main": "dist/map_controller.js",
"webpackMode": "lazy",
"fetch": "lazy",
"enabled": true
}
},
"importmap": {
"@hotwired/stimulus": "^3.0.0",
"@googlemaps/js-api-loader": "^1.16.6",
"@symfony/ux-map-google/map-controller": "path:%PACKAGE%/dist/map_controller.js"
}
},
"peerDependencies": {
"@googlemaps/js-api-loader": "^1.16.6",
"@hotwired/stimulus": "^3.0.0"
},
"peerDependenciesMeta": {
"@googlemaps/js-api-loader": {
"optional": false
}
},
"devDependencies": {
"@googlemaps/js-api-loader": "^1.16.6",
"@hotwired/stimulus": "^3.0.0",
"@types/google.maps": "^3.55.9",
"happy-dom": "^14.12.3"
}
}

View File

@@ -0,0 +1,187 @@
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import AbstractMapController from '@symfony/ux-map/abstract-map-controller';
import type { Point, MarkerDefinition } from '@symfony/ux-map/abstract-map-controller';
import type { LoaderOptions } from '@googlemaps/js-api-loader';
import { Loader } from '@googlemaps/js-api-loader';
type MapOptions = Pick<
google.maps.MapOptions,
| 'mapId'
| 'gestureHandling'
| 'backgroundColor'
| 'disableDoubleClickZoom'
| 'zoomControl'
| 'zoomControlOptions'
| 'mapTypeControl'
| 'mapTypeControlOptions'
| 'streetViewControl'
| 'streetViewControlOptions'
| 'fullscreenControl'
| 'fullscreenControlOptions'
>;
let loader: Loader;
let library: {
_Map: typeof google.maps.Map;
AdvancedMarkerElement: typeof google.maps.marker.AdvancedMarkerElement;
InfoWindow: typeof google.maps.InfoWindow;
};
export default class extends AbstractMapController<
MapOptions,
google.maps.Map,
google.maps.marker.AdvancedMarkerElement,
google.maps.InfoWindow
> {
static values = {
providerOptions: Object,
};
declare providerOptionsValue: Pick<
LoaderOptions,
'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version'
>;
async connect() {
if (!loader) {
loader = new Loader(this.providerOptionsValue);
}
const { Map: _Map, InfoWindow } = await loader.importLibrary('maps');
const { AdvancedMarkerElement } = await loader.importLibrary('marker');
library = { _Map, AdvancedMarkerElement, InfoWindow };
super.connect();
}
protected doCreateMap({
center,
zoom,
options,
}: {
center: Point;
zoom: number;
options: MapOptions;
}): google.maps.Map {
// We assume the following control options are enabled if their options are set
options.zoomControl = typeof options.zoomControlOptions !== 'undefined';
options.mapTypeControl = typeof options.mapTypeControlOptions !== 'undefined';
options.streetViewControl = typeof options.streetViewControlOptions !== 'undefined';
options.fullscreenControl = typeof options.fullscreenControlOptions !== 'undefined';
return new library._Map(this.element, {
...options,
center,
zoom,
});
}
protected doCreateMarker(
definition: MarkerDefinition<google.maps.marker.AdvancedMarkerElementOptions, google.maps.InfoWindowOptions>
): google.maps.marker.AdvancedMarkerElement {
const { position, title, infoWindow, rawOptions = {}, ...otherOptions } = definition;
const marker = new library.AdvancedMarkerElement({
position,
title,
...otherOptions,
...rawOptions,
map: this.map,
});
if (infoWindow) {
this.createInfoWindow({ definition: infoWindow, marker });
}
return marker;
}
protected doCreateInfoWindow({
definition,
marker,
}: {
definition: MarkerDefinition<
google.maps.marker.AdvancedMarkerElementOptions,
google.maps.InfoWindowOptions
>['infoWindow'];
marker: google.maps.marker.AdvancedMarkerElement;
}): google.maps.InfoWindow {
const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
const infoWindow = new library.InfoWindow({
headerContent: this.createTextOrElement(headerContent),
content: this.createTextOrElement(content),
...otherOptions,
...rawOptions,
});
if (definition.opened) {
infoWindow.open({
map: this.map,
shouldFocus: false,
anchor: marker,
});
}
marker.addListener('click', () => {
if (definition.autoClose) {
this.closeInfoWindowsExcept(infoWindow);
}
infoWindow.open({
map: this.map,
anchor: marker,
});
});
return infoWindow;
}
private createTextOrElement(content: string | null): string | HTMLElement | null {
if (!content) {
return null;
}
// we assume it's HTML if it includes "<"
if (content.includes('<')) {
const div = document.createElement('div');
div.innerHTML = content;
return div;
}
return content;
}
private closeInfoWindowsExcept(infoWindow: google.maps.InfoWindow) {
this.infoWindows.forEach((otherInfoWindow) => {
if (otherInfoWindow !== infoWindow) {
otherInfoWindow.close();
}
});
}
protected doFitBoundsToMarkers(): void {
if (this.markers.length === 0) {
return;
}
const bounds = new google.maps.LatLngBounds();
this.markers.forEach((marker) => {
if (!marker.position) {
return;
}
bounds.extend(marker.position);
});
this.map.fitBounds(bounds);
}
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Application, Controller } from '@hotwired/stimulus';
import { getByTestId, waitFor } from '@testing-library/dom';
import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
import GoogleController from '../src/map_controller';
// Controller used to check the actual controller was properly booted
class CheckController extends Controller {
connect() {
this.element.addEventListener('ux:map:pre-connect', (event) => {
this.element.classList.add('pre-connected');
});
this.element.addEventListener('ux:map:connect', (event) => {
this.element.classList.add('connected');
});
}
}
const startStimulus = () => {
const application = Application.start();
application.register('check', CheckController);
application.register('google', GoogleController);
};
describe('GoogleMapsController', () => {
let container: HTMLElement;
beforeEach(() => {
container = mountDOM(`
<div
data-testid="map"
data-controller="check google"
style="height&#x3A;&#x20;700px&#x3B;&#x20;margin&#x3A;&#x20;10px"
data-google-provider-options-value="&#x7B;&quot;language&quot;&#x3A;&quot;fr&quot;,&quot;region&quot;&#x3A;&quot;FR&quot;,&quot;retries&quot;&#x3A;10,&quot;version&quot;&#x3A;&quot;weekly&quot;,&quot;apiKey&quot;&#x3A;&quot;&quot;&#x7D;"
data-google-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;4,&quot;fitBoundsToMarkers&quot;&#x3A;true,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;&quot;YOUR_MAP_ID&quot;,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false,&quot;zoomControl&quot;&#x3A;true,&quot;zoomControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;mapTypeControl&quot;&#x3A;true,&quot;mapTypeControlOptions&quot;&#x3A;&#x7B;&quot;mapTypeIds&quot;&#x3A;&#x5B;&#x5D;,&quot;position&quot;&#x3A;14,&quot;style&quot;&#x3A;0&#x7D;,&quot;streetViewControl&quot;&#x3A;true,&quot;streetViewControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;fullscreenControl&quot;&#x3A;true,&quot;fullscreenControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;20&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Paris&quot;,&quot;infoWindow&quot;&#x3A;null&#x7D;,&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;45.764,&quot;lng&quot;&#x3A;4.8357&#x7D;,&quot;title&quot;&#x3A;&quot;Lyon&quot;,&quot;infoWindow&quot;&#x3A;&#x7B;&quot;headerContent&quot;&#x3A;&quot;&lt;b&gt;Lyon&lt;&#x5C;&#x2F;b&gt;&quot;,&quot;content&quot;&#x3A;&quot;The&#x20;French&#x20;town&#x20;in&#x20;the&#x20;historic&#x20;Rh&#x5C;u00f4ne-Alpes&#x20;region,&#x20;located&#x20;at&#x20;the&#x20;junction&#x20;of&#x20;the&#x20;Rh&#x5C;u00f4ne&#x20;and&#x20;Sa&#x5C;u00f4ne&#x20;rivers.&quot;,&quot;position&quot;&#x3A;null,&quot;opened&quot;&#x3A;false,&quot;autoClose&quot;&#x3A;true&#x7D;&#x7D;&#x5D;&#x7D;"
></div>
`);
});
afterEach(() => {
clearDOM();
});
it('connect', async () => {
const div = getByTestId(container, 'map');
expect(div).not.toHaveClass('pre-connected');
expect(div).not.toHaveClass('connected');
startStimulus();
await waitFor(() => expect(div).toHaveClass('pre-connected'));
await waitFor(() => expect(div).toHaveClass('connected'));
});
});

17
assets/vitest.config.js Normal file
View File

@@ -0,0 +1,17 @@
import { defineConfig, mergeConfig } from 'vitest/config';
import configShared from '../../../../../../vitest.config.js'
export default mergeConfig(
configShared,
defineConfig({
resolve: {
alias: {
'@symfony/ux-map/abstract-map-controller': __dirname + '/../../../../assets/src/abstract_map_controller.ts',
},
},
test: {
// We need a browser(-like) environment to run the tests
environment: 'happy-dom',
},
})
);

33
composer.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "symfony/ux-map-google",
"type": "symfony-ux-map-bridge",
"description": "Symfony UX Map GoogleMaps Bridge",
"keywords": ["google-maps", "map", "symfony", "ux"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Hugo Alliaume",
"email": "hugo@alliau.me"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=8.3",
"symfony/ux-map": "^2.19"
},
"require-dev": {
"symfony/phpunit-bridge": "^6.4|^7.0"
},
"autoload": {
"psr-4": { "Symfony\\UX\\Map\\Bridge\\Google\\": "src/" },
"exclude-from-classmap": []
},
"autoload-dev": {
"psr-4": { "Symfony\\UX\\Map\\Bridge\\Google\\Tests\\": "tests/" }
},
"minimum-stability": "dev"
}

26
phpunit.xml.dist Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/bin/.phpunit/phpunit.xsd"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php>
<ini name="error_reporting" value="-1"/>
<server name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0&amp;max[direct]=0"/>
</php>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>

157
src/GoogleOptions.php Normal file
View File

@@ -0,0 +1,157 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google;
use Symfony\UX\Map\Bridge\Google\Option\FullscreenControlOptions;
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlOptions;
use Symfony\UX\Map\Bridge\Google\Option\StreetViewControlOptions;
use Symfony\UX\Map\Bridge\Google\Option\ZoomControlOptions;
use Symfony\UX\Map\MapOptionsInterface;
/**
* @author Hugo Alliaume <hugo@alliau.me>
*/
final class GoogleOptions implements MapOptionsInterface
{
public function __construct(
private ?string $mapId = null,
private GestureHandling $gestureHandling = GestureHandling::AUTO,
private ?string $backgroundColor = null,
private bool $disableDoubleClickZoom = false,
private bool $zoomControl = true,
private ZoomControlOptions $zoomControlOptions = new ZoomControlOptions(),
private bool $mapTypeControl = true,
private MapTypeControlOptions $mapTypeControlOptions = new MapTypeControlOptions(),
private bool $streetViewControl = true,
private StreetViewControlOptions $streetViewControlOptions = new StreetViewControlOptions(),
private bool $fullscreenControl = true,
private FullscreenControlOptions $fullscreenControlOptions = new FullscreenControlOptions(),
) {
}
public function mapId(?string $mapId): self
{
$this->mapId = $mapId;
return $this;
}
public function gestureHandling(GestureHandling $gestureHandling): self
{
$this->gestureHandling = $gestureHandling;
return $this;
}
public function backgroundColor(?string $backgroundColor): self
{
$this->backgroundColor = $backgroundColor;
return $this;
}
public function doubleClickZoom(bool $enable = true): self
{
$this->disableDoubleClickZoom = !$enable;
return $this;
}
public function zoomControl(bool $enable = true): self
{
$this->zoomControl = $enable;
return $this;
}
public function zoomControlOptions(ZoomControlOptions $zoomControlOptions): self
{
$this->zoomControl = true;
$this->zoomControlOptions = $zoomControlOptions;
return $this;
}
public function mapTypeControl(bool $enable = true): self
{
$this->mapTypeControl = $enable;
return $this;
}
public function mapTypeControlOptions(MapTypeControlOptions $mapTypeControlOptions): self
{
$this->mapTypeControl = true;
$this->mapTypeControlOptions = $mapTypeControlOptions;
return $this;
}
public function streetViewControl(bool $enable = true): self
{
$this->streetViewControl = $enable;
return $this;
}
public function streetViewControlOptions(StreetViewControlOptions $streetViewControlOptions): self
{
$this->streetViewControl = true;
$this->streetViewControlOptions = $streetViewControlOptions;
return $this;
}
public function fullscreenControl(bool $enable = true): self
{
$this->fullscreenControl = $enable;
return $this;
}
public function fullscreenControlOptions(FullscreenControlOptions $fullscreenControlOptions): self
{
$this->fullscreenControl = true;
$this->fullscreenControlOptions = $fullscreenControlOptions;
return $this;
}
public function toArray(): array
{
$array = [
'mapId' => $this->mapId,
'gestureHandling' => $this->gestureHandling->value,
'backgroundColor' => $this->backgroundColor,
'disableDoubleClickZoom' => $this->disableDoubleClickZoom,
];
if ($this->zoomControl) {
$array['zoomControlOptions'] = $this->zoomControlOptions->toArray();
}
if ($this->mapTypeControl) {
$array['mapTypeControlOptions'] = $this->mapTypeControlOptions->toArray();
}
if ($this->streetViewControl) {
$array['streetViewControlOptions'] = $this->streetViewControlOptions->toArray();
}
if ($this->fullscreenControl) {
$array['fullscreenControlOptions'] = $this->fullscreenControlOptions->toArray();
}
return $array;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Option;
/**
* @see https://developers.google.com/maps/documentation/javascript/reference/control#ControlPosition
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
enum ControlPosition: int
{
/**
* Equivalent to bottom-center in both LTR and RTL.
*/
case BLOCK_END_INLINE_CENTER = 24;
/**
* Equivalent to bottom-right in LTR, or bottom-left in RTL.
*/
case BLOCK_END_INLINE_END = 25;
/**
* Equivalent to bottom-left in LTR, or bottom-right in RTL.
*/
case BLOCK_END_INLINE_START = 23;
/**
* Equivalent to top-center in both LTR and RTL.
*/
case BLOCK_START_INLINE_CENTER = 15;
/**
* Equivalent to top-right in LTR, or top-left in RTL.
*/
case BLOCK_START_INLINE_END = 16;
/**
* Equivalent to top-left in LTR, or top-right in RTL.
*/
case BLOCK_START_INLINE_START = 14;
/**
* Equivalent to right-center in LTR, or left-center in RTL.
*/
case INLINE_END_BLOCK_CENTER = 21;
/**
* Equivalent to right-bottom in LTR, or left-bottom in RTL.
*/
case INLINE_END_BLOCK_END = 22;
/**
* Equivalent to right-top in LTR, or left-top in RTL.
*/
case INLINE_END_BLOCK_START = 20;
/**
* Equivalent to left-center in LTR, or right-center in RTL.
*/
case INLINE_START_BLOCK_CENTER = 17;
/**
* Equivalent to left-bottom in LTR, or right-bottom in RTL.
*/
case INLINE_START_BLOCK_END = 19;
/**
* Equivalent to left-top in LTR, or right-top in RTL.
*/
case INLINE_START_BLOCK_START = 18;
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Option;
/**
* Options for the rendering of the fullscreen control.
*
* @see https://developers.google.com/maps/documentation/javascript/reference/control#FullscreenControlOptions
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
final readonly class FullscreenControlOptions
{
public function __construct(
private ControlPosition $position = ControlPosition::INLINE_END_BLOCK_START,
) {
}
public function toArray(): array
{
return [
'position' => $this->position->value,
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Option;
/**
* This setting controls how the API handles gestures on the map.
*
* @see https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.gestureHandling
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
enum GestureHandling: string
{
/**
* Scroll events and one-finger touch gestures scroll the page, and do not zoom or pan the map.
* Two-finger touch gestures pan and zoom the map.
* Scroll events with a ctrl key or ⌘ key pressed zoom the map.
* In this mode the map cooperates with the page.
*/
case COOPERATIVE = 'cooperative';
/**
* All touch gestures and scroll events pan or zoom the map.
*/
case GREEDY = 'greedy';
/**
* The map cannot be panned or zoomed by user gestures.
*/
case NONE = 'none';
/**
* Gesture handling is either cooperative or greedy, depending on whether the page is scrollable or in an iframe.
*/
case AUTO = 'auto';
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Option;
/**
* Options for the rendering of the map type control.
*
* @see https://developers.google.com/maps/documentation/javascript/reference/control#MapTypeControlOptions
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
final readonly class MapTypeControlOptions
{
/**
* @param array<'hybrid'|'roadmap'|'satellite'|'terrain'|string> $mapTypeIds
*/
public function __construct(
private array $mapTypeIds = [],
private ControlPosition $position = ControlPosition::BLOCK_START_INLINE_START,
private MapTypeControlStyle $style = MapTypeControlStyle::DEFAULT,
) {
}
public function toArray(): array
{
return [
'mapTypeIds' => $this->mapTypeIds,
'position' => $this->position->value,
'style' => $this->style->value,
];
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Option;
/**
* Identifiers for common MapTypesControls.
*
* @see https://developers.google.com/maps/documentation/javascript/reference/control#MapTypeControlStyle
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
enum MapTypeControlStyle: int
{
/**
* Uses the default map type control. When the DEFAULT control is shown, it will vary according to window size and other factors.
* The DEFAULT control may change in future versions of the API.
*/
case DEFAULT = 0;
/**
* A dropdown menu for the screen realestate conscious.
*/
case DROPDOWN_MENU = 2;
/**
* The standard horizontal radio buttons bar.
*/
case HORIZONTAL_BAR = 1;
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Option;
/**
* Options for the rendering of the Street View pegman control on the map.
*
* @see https://developers.google.com/maps/documentation/javascript/reference/control#StreetViewControlOptions
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
final readonly class StreetViewControlOptions
{
public function __construct(
private ControlPosition $position = ControlPosition::INLINE_END_BLOCK_END,
) {
}
public function toArray(): array
{
return [
'position' => $this->position->value,
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Option;
/**
* Options for the rendering of the zoom control.
*
* @see https://developers.google.com/maps/documentation/javascript/reference/control#ZoomControlOptions
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
final readonly class ZoomControlOptions
{
public function __construct(
private ControlPosition $position = ControlPosition::INLINE_END_BLOCK_END,
) {
}
public function toArray(): array
{
return [
'position' => $this->position->value,
];
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Renderer;
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
use Symfony\UX\Map\MapOptionsInterface;
use Symfony\UX\Map\Renderer\AbstractRenderer;
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
/**
* @author Hugo Alliaume <hugo@alliau.me>
*
* @internal
*/
final readonly class GoogleRenderer extends AbstractRenderer
{
/**
* Parameters are based from https://googlemaps.github.io/js-api-loader/interfaces/LoaderOptions.html documentation.
*/
public function __construct(
StimulusHelper $stimulusHelper,
#[\SensitiveParameter]
private string $apiKey,
private ?string $id = null,
private ?string $language = null,
private ?string $region = null,
private ?string $nonce = null,
private ?int $retries = null,
private ?string $url = null,
private ?string $version = null,
) {
parent::__construct($stimulusHelper);
}
protected function getName(): string
{
return 'google';
}
protected function getProviderOptions(): array
{
return array_filter([
'id' => $this->id,
'language' => $this->language,
'region' => $this->region,
'nonce' => $this->nonce,
'retries' => $this->retries,
'url' => $this->url,
'version' => $this->version,
]) + ['apiKey' => $this->apiKey];
}
protected function getDefaultMapOptions(): MapOptionsInterface
{
return new GoogleOptions();
}
public function __toString(): string
{
return \sprintf(
'google://%s@default/?%s',
str_repeat('*', \strlen($this->apiKey)),
http_build_query(array_filter([
'id' => $this->id,
'language' => $this->language,
'region' => $this->region,
'nonce' => $this->nonce,
'retries' => $this->retries,
'url' => $this->url,
'version' => $this->version,
]))
);
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Renderer;
use Symfony\UX\Map\Exception\InvalidArgumentException;
use Symfony\UX\Map\Exception\UnsupportedSchemeException;
use Symfony\UX\Map\Renderer\AbstractRendererFactory;
use Symfony\UX\Map\Renderer\Dsn;
use Symfony\UX\Map\Renderer\RendererFactoryInterface;
use Symfony\UX\Map\Renderer\RendererInterface;
/**
* @author Hugo Alliaume <hugo@alliau.me>
*/
final class GoogleRendererFactory extends AbstractRendererFactory implements RendererFactoryInterface
{
public function create(Dsn $dsn): RendererInterface
{
if (!$this->supports($dsn)) {
throw new UnsupportedSchemeException($dsn);
}
$apiKey = $dsn->getUser() ?: throw new InvalidArgumentException('The Google Maps renderer requires an API key as the user part of the DSN.');
return new GoogleRenderer(
$this->stimulus,
$apiKey,
id: $dsn->getOption('id'),
language: $dsn->getOption('language'),
region: $dsn->getOption('region'),
nonce: $dsn->getOption('nonce'),
retries: $dsn->getOption('retries'),
url: $dsn->getOption('url'),
version: $dsn->getOption('version', 'weekly'),
);
}
protected function getSupportedSchemes(): array
{
return ['google'];
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
class GoogleOptionsTest extends TestCase
{
public function testWithMinimalConfiguration(): void
{
$options = new GoogleOptions();
self::assertSame([
'mapId' => null,
'gestureHandling' => 'auto',
'backgroundColor' => null,
'disableDoubleClickZoom' => false,
'zoomControlOptions' => [
'position' => ControlPosition::INLINE_END_BLOCK_END->value,
],
'mapTypeControlOptions' => [
'mapTypeIds' => [],
'position' => ControlPosition::BLOCK_START_INLINE_START->value,
'style' => MapTypeControlStyle::DEFAULT->value,
],
'streetViewControlOptions' => [
'position' => ControlPosition::INLINE_END_BLOCK_END->value,
],
'fullscreenControlOptions' => [
'position' => ControlPosition::INLINE_END_BLOCK_START->value,
],
], $options->toArray());
}
public function testWithMinimalConfigurationAndWithoutControls(): void
{
$options = new GoogleOptions(
mapId: '2b2d73ba4b8c7b41',
gestureHandling: GestureHandling::GREEDY,
backgroundColor: '#f00',
disableDoubleClickZoom: true,
zoomControl: false,
mapTypeControl: false,
streetViewControl: false,
fullscreenControl: false,
);
self::assertSame([
'mapId' => '2b2d73ba4b8c7b41',
'gestureHandling' => GestureHandling::GREEDY->value,
'backgroundColor' => '#f00',
'disableDoubleClickZoom' => true,
], $options->toArray());
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests;
use Symfony\UX\Map\Bridge\Google\Renderer\GoogleRendererFactory;
use Symfony\UX\Map\Renderer\RendererFactoryInterface;
use Symfony\UX\Map\Test\RendererFactoryTestCase;
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
final class GoogleRendererFactoryTest extends RendererFactoryTestCase
{
public function createRendererFactory(): RendererFactoryInterface
{
return new GoogleRendererFactory(new StimulusHelper(null));
}
public static function supportsRenderer(): iterable
{
yield [true, 'google://GOOGLE_MAPS_API_KEY@default'];
yield [false, 'somethingElse://login:apiKey@default'];
}
public static function createRenderer(): iterable
{
yield [
'google://*******************@default/?version=weekly',
'google://GOOGLE_MAPS_API_KEY@default',
];
yield [
'google://*******************@default/?version=quartly',
'google://GOOGLE_MAPS_API_KEY@default?version=quartly',
];
}
public static function unsupportedSchemeRenderer(): iterable
{
yield ['somethingElse://foo@default'];
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests;
use Symfony\UX\Map\Bridge\Google\GoogleOptions;
use Symfony\UX\Map\Bridge\Google\Renderer\GoogleRenderer;
use Symfony\UX\Map\InfoWindow;
use Symfony\UX\Map\Map;
use Symfony\UX\Map\Marker;
use Symfony\UX\Map\Point;
use Symfony\UX\Map\Test\RendererTestCase;
use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
class GoogleRendererTest extends RendererTestCase
{
public function provideTestRenderMap(): iterable
{
$map = (new Map())
->center(new Point(48.8566, 2.3522))
->zoom(12);
yield 'simple map, with minimum options' => [
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="&#x7B;&quot;apiKey&quot;&#x3A;&quot;api_key&quot;&#x7D;" data-symfony--ux-map-google--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;null,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false,&quot;zoomControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;mapTypeControlOptions&quot;&#x3A;&#x7B;&quot;mapTypeIds&quot;&#x3A;&#x5B;&#x5D;,&quot;position&quot;&#x3A;14,&quot;style&quot;&#x3A;0&#x7D;,&quot;streetViewControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;fullscreenControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;20&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x5D;&#x7D;"></div>',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => $map,
];
yield 'with every options' => [
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="&#x7B;&quot;id&quot;&#x3A;&quot;gmap&quot;,&quot;language&quot;&#x3A;&quot;fr&quot;,&quot;region&quot;&#x3A;&quot;FR&quot;,&quot;nonce&quot;&#x3A;&quot;abcd&quot;,&quot;retries&quot;&#x3A;10,&quot;url&quot;&#x3A;&quot;https&#x3A;&#x5C;&#x2F;&#x5C;&#x2F;maps.googleapis.com&#x5C;&#x2F;maps&#x5C;&#x2F;api&#x5C;&#x2F;js&quot;,&quot;version&quot;&#x3A;&quot;quarterly&quot;,&quot;apiKey&quot;&#x3A;&quot;api_key&quot;&#x7D;" data-symfony--ux-map-google--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;null,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false,&quot;zoomControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;mapTypeControlOptions&quot;&#x3A;&#x7B;&quot;mapTypeIds&quot;&#x3A;&#x5B;&#x5D;,&quot;position&quot;&#x3A;14,&quot;style&quot;&#x3A;0&#x7D;,&quot;streetViewControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;fullscreenControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;20&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x5D;&#x7D;"></div>',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key', id: 'gmap', language: 'fr', region: 'FR', nonce: 'abcd', retries: 10, url: 'https://maps.googleapis.com/maps/api/js', version: 'quarterly'),
'map' => $map,
];
yield 'with markers and infoWindows' => [
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="&#x7B;&quot;apiKey&quot;&#x3A;&quot;api_key&quot;&#x7D;" data-symfony--ux-map-google--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;null,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false,&quot;zoomControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;mapTypeControlOptions&quot;&#x3A;&#x7B;&quot;mapTypeIds&quot;&#x3A;&#x5B;&#x5D;,&quot;position&quot;&#x3A;14,&quot;style&quot;&#x3A;0&#x7D;,&quot;streetViewControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;fullscreenControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;20&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Paris&quot;,&quot;infoWindow&quot;&#x3A;null&#x7D;,&#x7B;&quot;position&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;title&quot;&#x3A;&quot;Lyon&quot;,&quot;infoWindow&quot;&#x3A;&#x7B;&quot;headerContent&quot;&#x3A;null,&quot;content&quot;&#x3A;&quot;Lyon&quot;,&quot;position&quot;&#x3A;null,&quot;opened&quot;&#x3A;false,&quot;autoClose&quot;&#x3A;true&#x7D;&#x7D;&#x5D;&#x7D;"></div>',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => (clone $map)
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
->addMarker(new Marker(new Point(48.8566, 2.3522), 'Lyon', infoWindow: new InfoWindow(content: 'Lyon'))),
];
yield 'with controls enabled' => [
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="&#x7B;&quot;apiKey&quot;&#x3A;&quot;api_key&quot;&#x7D;" data-symfony--ux-map-google--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;null,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false,&quot;zoomControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;mapTypeControlOptions&quot;&#x3A;&#x7B;&quot;mapTypeIds&quot;&#x3A;&#x5B;&#x5D;,&quot;position&quot;&#x3A;14,&quot;style&quot;&#x3A;0&#x7D;,&quot;streetViewControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;22&#x7D;,&quot;fullscreenControlOptions&quot;&#x3A;&#x7B;&quot;position&quot;&#x3A;20&#x7D;&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x5D;&#x7D;"></div>',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => (clone $map)
->options(new GoogleOptions(
zoomControl: true,
mapTypeControl: true,
streetViewControl: true,
fullscreenControl: true,
)),
];
yield 'without controls enabled' => [
'expected_render' => '<div data-controller="symfony--ux-map-google--map" data-symfony--ux-map-google--map-provider-options-value="&#x7B;&quot;apiKey&quot;&#x3A;&quot;api_key&quot;&#x7D;" data-symfony--ux-map-google--map-view-value="&#x7B;&quot;center&quot;&#x3A;&#x7B;&quot;lat&quot;&#x3A;48.8566,&quot;lng&quot;&#x3A;2.3522&#x7D;,&quot;zoom&quot;&#x3A;12,&quot;fitBoundsToMarkers&quot;&#x3A;false,&quot;options&quot;&#x3A;&#x7B;&quot;mapId&quot;&#x3A;null,&quot;gestureHandling&quot;&#x3A;&quot;auto&quot;,&quot;backgroundColor&quot;&#x3A;null,&quot;disableDoubleClickZoom&quot;&#x3A;false&#x7D;,&quot;markers&quot;&#x3A;&#x5B;&#x5D;&#x7D;"></div>',
'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
'map' => (clone $map)
->options(new GoogleOptions(
zoomControl: false,
mapTypeControl: false,
streetViewControl: false,
fullscreenControl: false,
)),
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests\Option;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
class ControlPositionTest extends TestCase
{
public function testEnumValues(): void
{
self::assertSame(24, ControlPosition::BLOCK_END_INLINE_CENTER->value);
self::assertSame(25, ControlPosition::BLOCK_END_INLINE_END->value);
self::assertSame(23, ControlPosition::BLOCK_END_INLINE_START->value);
self::assertSame(15, ControlPosition::BLOCK_START_INLINE_CENTER->value);
self::assertSame(16, ControlPosition::BLOCK_START_INLINE_END->value);
self::assertSame(14, ControlPosition::BLOCK_START_INLINE_START->value);
self::assertSame(21, ControlPosition::INLINE_END_BLOCK_CENTER->value);
self::assertSame(22, ControlPosition::INLINE_END_BLOCK_END->value);
self::assertSame(20, ControlPosition::INLINE_END_BLOCK_START->value);
self::assertSame(17, ControlPosition::INLINE_START_BLOCK_CENTER->value);
self::assertSame(19, ControlPosition::INLINE_START_BLOCK_END->value);
self::assertSame(18, ControlPosition::INLINE_START_BLOCK_START->value);
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests\Option;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
use Symfony\UX\Map\Bridge\Google\Option\FullscreenControlOptions;
class FullscreenControlOptionsTest extends TestCase
{
public function testToArray(): void
{
$options = new FullscreenControlOptions(
position: ControlPosition::BLOCK_END_INLINE_CENTER
);
self::assertSame([
'position' => ControlPosition::BLOCK_END_INLINE_CENTER->value,
], $options->toArray());
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests\Option;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\Option\GestureHandling;
class GestureHandlingTest extends TestCase
{
public function testEnumValues(): void
{
self::assertSame('cooperative', GestureHandling::COOPERATIVE->value);
self::assertSame('greedy', GestureHandling::GREEDY->value);
self::assertSame('none', GestureHandling::NONE->value);
self::assertSame('auto', GestureHandling::AUTO->value);
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests\Option;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlOptions;
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
class MapTypeControlOptionsTest extends TestCase
{
public function testToArray(): void
{
$options = new MapTypeControlOptions(
mapTypeIds: ['satellite', 'hybrid'],
position: ControlPosition::BLOCK_END_INLINE_END,
style: MapTypeControlStyle::HORIZONTAL_BAR,
);
self::assertSame([
'mapTypeIds' => ['satellite', 'hybrid'],
'position' => ControlPosition::BLOCK_END_INLINE_END->value,
'style' => MapTypeControlStyle::HORIZONTAL_BAR->value,
], $options->toArray());
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests\Option;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\Option\MapTypeControlStyle;
class MapTypeControlStyleTest extends TestCase
{
public function testEnumValues(): void
{
self::assertSame(0, MapTypeControlStyle::DEFAULT->value);
self::assertSame(2, MapTypeControlStyle::DROPDOWN_MENU->value);
self::assertSame(1, MapTypeControlStyle::HORIZONTAL_BAR->value);
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests\Option;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
use Symfony\UX\Map\Bridge\Google\Option\StreetViewControlOptions;
class StreetViewControlOptionsTest extends TestCase
{
public function testToArray(): void
{
$options = new StreetViewControlOptions(
position: ControlPosition::INLINE_END_BLOCK_CENTER
);
self::assertSame([
'position' => ControlPosition::INLINE_END_BLOCK_CENTER->value,
], $options->toArray());
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\UX\Map\Bridge\Google\Tests\Option;
use PHPUnit\Framework\TestCase;
use Symfony\UX\Map\Bridge\Google\Option\ControlPosition;
use Symfony\UX\Map\Bridge\Google\Option\ZoomControlOptions;
class ZoomControlOptionsTest extends TestCase
{
public function testToArray(): void
{
$options = new ZoomControlOptions(
position: ControlPosition::BLOCK_START_INLINE_END,
);
self::assertSame([
'position' => ControlPosition::BLOCK_START_INLINE_END->value,
], $options->toArray());
}
}