Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
DGE
DATAtourisme
webapp-quality
Commits
7af5cbc1
Commit
7af5cbc1
authored
Feb 01, 2022
by
Blaise de Carné
Browse files
feat: sync last commits
parent
baeec2f4
Pipeline
#22285
failed with stage
in 9 seconds
Changes
13
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
assets/react/components/ResultMap.tsx
View file @
7af5cbc1
...
...
@@ -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
,
setCurrentL
ayer
]
=
use
State
<
WebGLPointsLayer
<
any
>
|
undefined
>
();
const
map
=
useRef
<
Map
>
(
null
);
const
l
ayer
=
use
Ref
<
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
'
;
tooltip
Element
.
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
:
tooltip
Element
.
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
);
tooltip
Element
.
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
>
);
tooltip
Element
.
current
!
.
innerHTML
=
getTooltipContent
(
feature
as
Feature
<
Point
>
);
}
else
{
map
.
getViewport
().
style
.
cursor
=
''
;
map
.
current
.
getViewport
().
style
.
cursor
=
''
;
style
.
current
.
variables
.
hoveredFeatureId
=
""
;
}
// current
Layer
?.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
(
current
Layer
)
{
map
.
removeLayer
(
current
Layer
);
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
));
}
...
...
assets/react/dashboard/components/MapWidget.tsx
View file @
7af5cbc1
...
...
@@ -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
>
...
...
assets/react/explorer/components/CustomResultMap.tsx
View file @
7af5cbc1
...
...
@@ -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
"
))
}
/>
/>
);
};
...
...
assets/styles/core/_helpers.scss
View file @
7af5cbc1
...
...
@@ -70,9 +70,6 @@
}
// colors
.bg-white
{
background-color
:
#FFF
!
important
;
}
.bg-dge-primary
{
background-color
:
$dge-primary
!
important
;
color
:
#fff
!
important
;
...
...
src/Command/CreateUserCommand.php
View file @
7af5cbc1
...
...
@@ -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
;
}
...
...
src/Controller/Administration/UserController.php
View file @
7af5cbc1
...
...
@@ -61,7 +61,7 @@ class UserController extends AbstractController
*/
public
function
add
(
Producer
$producer
=
null
)
{
return
new
Response
(
"todo"
);
dd
(
"todo"
);
}
/**
...
...
src/Controller/
Administration
/NewsController.php
→
src/Controller/
Help
/NewsController.php
View file @
7af5cbc1
...
...
@@ -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
,
]);
}
...
...
src/Menu/MenuBuilder.php
View file @
7af5cbc1
...
...
@@ -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
;
}
}
templates/dashboard/index.html.twig
View file @
7af5cbc1
...
...
@@ -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
-
%}
...
...
templates/
admin
/news/detail.html.twig
→
templates/
help
/news/detail.html.twig
View file @
7af5cbc1
File moved
templates/
admin
/news/edit.html.twig
→
templates/
help
/news/edit.html.twig
View file @
7af5cbc1
File moved
templates/
admin
/news/index.html.twig
→
templates/
help
/news/index.html.twig
View file @
7af5cbc1
{%
extends
"_layout.html.twig"
%}
{%
block
title
%}
Actualités
{%
endblock
%}
{%
set
_breadcrumb
=
[
'A
dministration
'
,
'Actualités'
]
%}
{%
set
_breadcrumb
=
[
'A
ide
'
,
'Actualités'
]
%}
{%
block
content
%}
...
...
templates/
admin
/news/remove.html.twig
→
templates/
help
/news/remove.html.twig
View file @
7af5cbc1
File moved
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment