Commit 7af5cbc1 authored by Blaise de Carné's avatar Blaise de Carné
Browse files

feat: sync last commits

parent baeec2f4
Pipeline #22285 failed with stage
in 9 seconds
......@@ -35,6 +35,7 @@ import Select, { SelectEvent } from "ol/interaction/Select";
import { click } from "ol/events/condition";
import { ReactiveComponentRenderProps } from "../interfaces";
import { BiInfoCircle } from "react-icons/bi";
export type ResultMapProps = {
dataField: string;
......@@ -92,13 +93,14 @@ const parseExtent = (value: string): number[] => {
const ResultMapComponent = (
props: ReactiveComponentRenderProps & ResultMapProps
) => {
const { dataField, data, createFeature, onFeatureClick, selectedFeatureId, getTooltipContent, loading, setQuery, value } = props;
const { dataField, data, createFeature, onFeatureClick, selectedFeatureId, getTooltipContent, loading, setQuery, value, size } = props;
const mapElement = useRef<HTMLDivElement>(null);
const tooltipElement = useRef<HTMLDivElement>(null);
const center = [1.71, 46.71];
const zoom = 6;
const [map, setMap] = useState<Map | undefined>();
const [currentLayer, setCurrentLayer] = useState<WebGLPointsLayer<any> | undefined>();
const map = useRef<Map>(null);
const layer = useRef<WebGLPointsLayer<any>>();
const initialExtent = value ? parseExtent(value) : undefined;
const [extent, setExtent] = useState<number[] | undefined>(initialExtent);
......@@ -106,7 +108,6 @@ const ResultMapComponent = (
// some refs
const select = useRef<Select>(new Select({ condition: click }));
const timeout = useRef<any>(null);
const tooltip = useRef<any>(null);
// see https://openlayers.org/en/latest/examples/webgl-points-layer.html
const style = useRef({
......@@ -138,7 +139,7 @@ const ResultMapComponent = (
// initialize map on first render - logic formerly put into componentDidMount
useEffect(() => {
// create map
const map = new Map({
const _map = new Map({
target: mapElement.current!,
layers: [
new TileLayer({
......@@ -156,16 +157,16 @@ const ResultMapComponent = (
});
if (initialExtent) {
map.getView().fit(initialExtent);
_map.getView().fit(initialExtent);
}
map.on('moveend', (evt: MapEvent) => {
_map.on('moveend', (evt: MapEvent) => {
timeout.current = setTimeout(() => {
setExtent(evt.map.getView().calculateExtent());
}, 500);
});
map.on('movestart', (evt: MapEvent) => {
_map.on('movestart', (evt: MapEvent) => {
if (timeout.current) {
clearTimeout(timeout.current);
timeout.current = null;
......@@ -173,41 +174,40 @@ const ResultMapComponent = (
});
select.current.on("select", (e: SelectEvent) => {
tooltip.current!.style.display = 'none';
tooltipElement.current!.style.display = 'none';
onFeatureClick && onFeatureClick(e.selected.length ? e.selected[0] : null);
});
map.addInteraction(select.current);
_map.addInteraction(select.current);
tooltip.current = document.getElementById('map-tooltip');
var overlay = new Overlay({
element: tooltip.current!,
element: tooltipElement.current!,
offset: [10, 0],
positioning: 'bottom-left'
});
map.addOverlay(overlay);
_map.addOverlay(overlay);
map.on('pointermove', (evt: MapBrowserEvent<PointerEvent>) => {
_map.on('pointermove', (evt: MapBrowserEvent<PointerEvent>) => {
if (!getTooltipContent) {
return;
}
var pixel = evt.pixel;
var feature = map.forEachFeatureAtPixel(pixel, feature => feature);
tooltip.current!.style.display = feature ? '' : 'none';
var feature = map.current.forEachFeatureAtPixel(pixel, feature => feature);
tooltipElement.current!.style.display = feature ? '' : 'none';
if (feature) {
map.getViewport().style.cursor = 'pointer';
map.current.getViewport().style.cursor = 'pointer';
style.current.variables.hoveredFeatureId = feature.get("id");
overlay.setPosition(evt.coordinate);
tooltip.current!.innerHTML = getTooltipContent(feature as Feature<Point>);
tooltipElement.current!.innerHTML = getTooltipContent(feature as Feature<Point>);
} else {
map.getViewport().style.cursor = '';
map.current.getViewport().style.cursor = '';
style.current.variables.hoveredFeatureId = "";
}
// currentLayer?.changed(); DOES NOT WORK
// layer.current?.changed(); DOES NOT WORK
});
// save map and vector layer references to state
setMap(map)
map.current = _map;
}, []);
// update markers
......@@ -215,18 +215,18 @@ const ResultMapComponent = (
if (map) {
const features = data.map((hit: any) => createFeature(hit));
const layer = new WebGLPointsLayer({
const _layer = new WebGLPointsLayer({
source : new VectorSource({ features }),
style: style.current,
disableHitDetection: false,
});
if (currentLayer) {
map.removeLayer(currentLayer);
if (layer.current) {
map.current.removeLayer(layer.current);
}
map.addLayer(layer);
setCurrentLayer(layer);
map.current.addLayer(_layer);
layer.current = _layer;
}
}, [JSON.stringify(data)]);
......@@ -240,8 +240,11 @@ const ResultMapComponent = (
}
}, [extent]);
return (<div ref={mapElement} className="map-container w-full h-full">
<div id="map-tooltip" className="bg-black bg-opacity-80 text-white py-1 px-2 shadow-xl rounded-md"></div>
return (<div ref={mapElement} className="map-container w-full h-full relative">
<div className='absolute z-10 bottom-0 left-0 bg-white bg-opacity-80 px-2 py-1 text-xs flex items-center'>
<BiInfoCircle className="mr-1" /><span>Le nombre de points affichés à l'écran est limité à <strong>{size}</strong></span>
</div>
<div ref={tooltipElement} className="bg-black bg-opacity-80 text-white py-1 px-2 shadow-xl rounded-md"></div>
</div>)
};
......@@ -252,7 +255,7 @@ const ResultMap = (props: ResultMapProps) => {
const { includeFields, dataField, size } = props;
const defaultQuery = (value: any) => {
let query: any = { match_all: {} };
let query: any = {exists: {field: dataField}};
if (value) {
query = getQueryForExtent(dataField, parseExtent(value));
}
......
......@@ -7,42 +7,95 @@
*/
import useAxios from "axios-hooks";
import { useContext, useEffect, useState } from "react";
import { BiMapPin } from "react-icons/bi";
import { DashboardContext } from "../Dashboard";
import { useEffect, useRef } from "react";
import Map from 'ol/Map'
import { fromLonLat } from 'ol/proj'
import View from 'ol/View'
import TileLayer from 'ol/layer/Tile'
import HeatmapLayer from 'ol/layer/Heatmap';
import VectorSource from 'ol/source/Vector'
import OSM from 'ol/source/OSM'
// import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import {defaults as defaultControls} from 'ol/control';
import Layer from "ol/layer/Layer";
const createFeature = (item: any): Feature<Point> => {
const { lon, lat, label } = item;
return new Feature({ geometry: new Point(fromLonLat([lon,lat])) })
}
const MapWidget = () => {
const { colors } = useContext(DashboardContext);
const [{ data, loading, error }, refetch] = useAxios("/dashboard/data/map");
const [geojson, setGeojson] = useState<any>({});
const [viewport, setViewport] = useState<any>({
longitude: 2.209666999999996,
latitude: 46.47484325155567,
zoom: 4
});
// const [hoverInfo, setHoverInfo] = useState(null);
const mapElement = useRef<HTMLDivElement>(null);
const map = useRef<Map>(null);
const layer = useRef<Layer<any, any>>(null);
const center = [1.71, 46.71];
const zoom = 4;
// initialize map on first render - logic formerly put into componentDidMount
useEffect(() => {
if (data) {
const bounds = { xMin: null, xMax: null, yMin: null, yMax: null};
const features = (data as any[]).map((d) => {
const { lon, lat, label } = d;
bounds.xMin = bounds.xMin && bounds.xMin < lon ? bounds.xMin : lon;
bounds.xMax = bounds.xMax && bounds.xMax > lon ? bounds.xMax : lon;
bounds.yMin = bounds.yMin && bounds.yMin < lat ? bounds.yMin : lat;
bounds.yMax = bounds.yMax && bounds.yMax > lat ? bounds.yMax : lat;
return { type: "Feature", geometry: { type: "Point", coordinates: [lon, lat] }, properties: { label } };
});
setGeojson({
type: "FeatureCollection",
features: features,
// create map
const _map = new Map({
target: mapElement.current!,
layers: [
new TileLayer({
source: new OSM()
})
],
view: new View({
projection: 'EPSG:3857',
center: fromLonLat(center),
zoom
}),
controls: defaultControls({
zoom: true
}),
});
// save map and vector layer references to state
map.current = _map;
}, []);
// update data
useEffect(() => {
if (map.current && data) {
const features = data.map((hit: any) => createFeature(hit));
const _layer = new HeatmapLayer({
source : new VectorSource({ features }),
blur: 15,
radius: 2
});
// todo : fit bounds
}
}, [data]);
// const _layer = new WebGLPointsLayer({
// source : new VectorSource({ features }),
// style: {
// symbol: {
// symbolType: 'circle',
// size: ['interpolate', ['exponential', 5], ['zoom'], 2, 4, 14, 12],
// color: '#c02846',
// offset: [0, 0],
// opacity: 0.95
// }
// },
// disableHitDetection: false,
// });
if (layer.current) {
map.current.removeLayer(layer.current);
}
map.current.addLayer(_layer);
layer.current = _layer;
map.current.getView().fit(layer.current.getSource().getExtent());
}
}, [map, data]);
return (
<div className={`widget ${loading ? "loading" : ""}`}>
......@@ -51,10 +104,7 @@ const MapWidget = () => {
<h3 className="box-title">Carte de vos POI</h3>
</div>
<div className="box-body p-0 h-[360px]">
<div className="flex flex-col items-center justify-center h-full">
<BiMapPin className="w-16 h-16 text-gray-300" />
<div className="text-gray-500 mt-3 text-sm">En cours de développement...</div>
</div>
<div ref={mapElement} className="map-container w-full h-full" />
</div>
</div>
</div>
......
......@@ -38,7 +38,7 @@ const CustomResultMap = (props: CustomResultMapProps) => {
getTooltipContent={(f: Feature<Point>) => f.get('hit')['label']['@fr']}
{...props}
onFeatureClick={(f: Feature<Point>) => onFeatureClick(f.get("hit"))}
/>
/>
);
};
......
......@@ -70,9 +70,6 @@
}
// colors
.bg-white {
background-color: #FFF !important;
}
.bg-dge-primary {
background-color: $dge-primary !important;
color: #fff !important;
......
......@@ -45,8 +45,8 @@ class CreateUserCommand extends Command
{
// email
$helper = $this->getHelper('question');
$question = new Question('Email :');
$question->setValidator(function($email) {
$question = new Question('Email : ');
$question->setValidator(function ($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \RuntimeException('Adresse email invalide');
}
......@@ -61,29 +61,29 @@ class CreateUserCommand extends Command
// nom
$helper = $this->getHelper('question');
$question = new Question('Nom :');
$question = new Question('Nom : ');
$lastName = $helper->ask($input, $output, $question);
// nom
$helper = $this->getHelper('question');
$question = new Question('Prénom :');
$question = new Question('Prénom : ');
$firstName = $helper->ask($input, $output, $question);
// organization
$helper = $this->getHelper('question');
$question = new Question('Organisation :', 'Non renseignée');
$question = new Question('Organisation : ', 'Non renseignée');
$organization = $helper->ask($input, $output, $question);
// organization_type
$helper = $this->getHelper('question');
$repository = $this->em->getRepository(OrganizationType::class);
$question = new ChoiceQuestion("Type d'organanisation", $repository->findAll(), 0);
$question = new ChoiceQuestion("Type d'organanisation", $repository->findAll(), 0);
$organizationType = $helper->ask($input, $output, $question);
// role
$helper = $this->getHelper('question');
$question = new Question('Role (ROLE_USER) :', 'ROLE_USER');
$question->setValidator(function($role) {
$question = new Question('Role (ROLE_USER) : ', 'ROLE_USER');
$question->setValidator(function ($role) {
if (!in_array($role, ['ROLE_USER', 'ROLE_ADMIN', 'ROLE_PRODUCER'])) {
throw new \RuntimeException('Role ' . $role . ' does not exists.');
}
......@@ -93,7 +93,7 @@ class CreateUserCommand extends Command
// password
$helper = $this->getHelper('question');
$question = new Question('Veuillez saisir un mot de passe :');
$question = new Question('Veuillez saisir un mot de passe : ');
$question->setHidden(true);
$question->setHiddenFallback(false);
$password = $helper->ask($input, $output, $question);
......@@ -111,7 +111,7 @@ class CreateUserCommand extends Command
$this->em->persist($user);
$this->em->flush();
$output->writeln('Utilisateur correctement généré!');
$output->writeln('Utilisateur correctement généré !');
return Command::SUCCESS;
}
......
......@@ -61,7 +61,7 @@ class UserController extends AbstractController
*/
public function add(Producer $producer = null)
{
return new Response("todo");
dd("todo");
}
/**
......
......@@ -4,11 +4,11 @@
* This file is part of the DATAtourisme project.
* 2022
* @author Conjecto <contact@conjecto.com>
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: GPL-3.0-or-later
* For the full copyright and license information, please view the LICENSE file that was distributed with this source code.
*/
namespace App\Controller\Administration;
namespace App\Controller\Help;
use App\Entity\News;
use App\Form\Type\NewsType;
......@@ -17,7 +17,6 @@ use App\Repository\NewsRepository;
use App\Repository\UserRepository;
use App\Service\FileUploader;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
......@@ -29,8 +28,7 @@ use Symfony\Component\Notifier\NotifierInterface;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/news", name="news")
* @Security("is_granted('ROLE_ADMIN') or is_granted('ROLE_PRODUCER')")
* @Route("/help/news", name="news")
*/
class NewsController extends AbstractController
{
......@@ -54,7 +52,7 @@ class NewsController extends AbstractController
$news = $repository->findBy([], ['date' => 'DESC', 'id' => 'DESC']);
}
return $this->render('admin/news/index.html.twig', [
return $this->render('help/news/index.html.twig', [
'months' => $months,
'active_month' => $month,
'total' => array_sum($months),
......@@ -72,7 +70,7 @@ class NewsController extends AbstractController
*/
public function detail(News $news, Request $request): Response
{
return $this->render('admin/news/detail.html.twig', [
return $this->render('help/news/detail.html.twig', [
'news' => $news
]);
}
......@@ -143,7 +141,7 @@ class NewsController extends AbstractController
return $this->redirectToRoute('news.index');
}
return $this->render('admin/news/edit.html.twig', [
return $this->render('help/news/edit.html.twig', [
'form' => $form->createView(),
]);
}
......@@ -192,7 +190,7 @@ class NewsController extends AbstractController
], 200);
}
return $this->render('admin/news/remove.html.twig', [
return $this->render('help/news/remove.html.twig', [
'news' => $news,
]);
}
......
......@@ -15,14 +15,10 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class MenuBuilder
{
/**
* @var FactoryInterface
*/
/** @var FactoryInterface */
private $factory;
/**
* @var AuthorizationChecker
*/
/** @var AuthorizationCheckerInterface */
private $authorizationChecker;
/**
......@@ -48,7 +44,7 @@ class MenuBuilder
],
]);
// Dashboard
// Explorer
$menu->addChild('Explorer', [
'route' => 'explorer.index',
'extras' => [
......@@ -67,9 +63,18 @@ class MenuBuilder
]);
$admin->addChild('Utilisateurs', ['route' => 'user.index']);
$admin->addChild('Actualités', ['route' => 'news.index']);
}
// Help
$help = $menu->addChild('Aide', [
'uri' => '#',
'extras' => [
'uri_prefix' => '/help',
'icon' => 'fa fa-question-circle',
],
]);
$help->addChild('Actualités', ['route' => 'news.index']);
return $menu;
}
}
......@@ -16,6 +16,7 @@
{%- block stylesheets -%}
{{ parent() }}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.12.0/css/ol.css" type="text/css">
{{ encore_entry_link_tags('react/dashboard') }}
{%- endblock -%}
......
{% extends "_layout.html.twig" %}
{% block title %}Actualités{% endblock %}
{% set _breadcrumb = ['Administration', 'Actualités'] %}
{% set _breadcrumb = ['Aide', 'Actualités'] %}
{% block content %}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment