add projects
This commit is contained in:
parent
cf5c37975e
commit
1717ce5c2c
|
@ -18,11 +18,13 @@ core:
|
|||
- {name: 'Blog\PostFollowController::enable', action: 'App\Controller\Blog\PostFollowController::enable'}
|
||||
- {name: 'Blog\PostFollowController::disable', action: 'App\Controller\Blog\PostFollowController::disable'}
|
||||
- {name: 'StlMeshController::meshes', action: 'App\Controller\StlMeshController::meshes'}
|
||||
- {name: 'ProjectController::projects', action: 'App\Controller\ProjectController::projects'}
|
||||
pages:
|
||||
App\Entity\Page\SimplePage:
|
||||
name: 'Page de contenu'
|
||||
templates:
|
||||
- {name: "Par défaut", file: "page/simple/default.html.twig"}
|
||||
- {name: "Projets", file: "page/simple/projects.html.twig"}
|
||||
App\Entity\Page\PostPage:
|
||||
name: 'Page article'
|
||||
templates:
|
||||
|
|
149
src/Controller/ProjectAdminController.php
Normal file
149
src/Controller/ProjectAdminController.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Core\Controller\Admin\Crud\CrudController;
|
||||
use App\Core\Crud\CrudConfiguration;
|
||||
use App\Core\Crud\Field;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Entity\Project as Entity;
|
||||
use App\Factory\ProjectFactory as Factory;
|
||||
use App\Form\ProjectType as Type;
|
||||
use App\Repository\ProjectRepositoryQuery as RepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use App\Entity\Project;
|
||||
|
||||
class ProjectAdminController extends CrudController
|
||||
{
|
||||
/**
|
||||
* @Route("/admin/project/{page}", name="admin_project_index", methods={"GET"}, requirements={"page":"\d+"})
|
||||
*/
|
||||
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doIndex($page, $query, $request, $session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/new", name="admin_project_new", methods={"GET", "POST"})
|
||||
*/
|
||||
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doNew($factory->create(), $entityManager, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/show/{entity}", name="admin_project_show", methods={"GET"})
|
||||
*/
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
return $this->doShow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/filter", name="admin_project_filter", methods={"GET"})
|
||||
*/
|
||||
public function filter(Session $session): Response
|
||||
{
|
||||
return $this->doFilter($session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/edit/{entity}", name="admin_project_edit", methods={"GET", "POST"})
|
||||
*/
|
||||
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doEdit($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/sort/{page}", name="admin_project_sort", methods={"POST"}, requirements={"page":"\d+"})
|
||||
*/
|
||||
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doSort($page, $query, $entityManager, $request, $session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/batch/{page}", name="admin_project_batch", methods={"POST"}, requirements={"page":"\d+"})
|
||||
*/
|
||||
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
|
||||
{
|
||||
return $this->doBatch($page, $query, $entityManager, $request, $session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/project/delete/{entity}", name="admin_project_delete", methods={"DELETE"})
|
||||
*/
|
||||
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||
{
|
||||
return $this->doDelete($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Projets')
|
||||
->setPageTitle('edit', '{label}')
|
||||
->setPageTitle('new', 'Nouveau projet')
|
||||
// ->setPageTitle('show', 'View of {id}')
|
||||
|
||||
->setPageRoute('index', 'admin_project_index')
|
||||
->setPageRoute('new', 'admin_project_new')
|
||||
->setPageRoute('edit', 'admin_project_edit')
|
||||
// ->setPageRoute('show', 'admin_project_show')
|
||||
->setPageRoute('sort', 'admin_project_sort')
|
||||
->setPageRoute('batch', 'admin_project_batch')
|
||||
->setPageRoute('delete', 'admin_project_delete')
|
||||
->setPageRoute('filter', 'admin_project_filter')
|
||||
|
||||
->setForm('edit', Type::class, [])
|
||||
->setForm('new', Type::class)
|
||||
// ->setForm('filter', Type::class)
|
||||
|
||||
// ->setMaxPerPage('index', 20)
|
||||
|
||||
->setIsSortableCollection('index', true)
|
||||
// ->setSortableCollectionProperty('sortOrder')
|
||||
|
||||
// ->setAction('index', 'new', true)
|
||||
->setAction('index', 'show', false)
|
||||
// ->setAction('index', 'edit', true)
|
||||
// ->setAction('index', 'delete', true)
|
||||
|
||||
// ->setAction('edit', 'back', true)
|
||||
->setAction('edit', 'show', false)
|
||||
// ->setAction('edit', 'delete', true)
|
||||
|
||||
// ->setAction('show', 'back', true)
|
||||
// ->setAction('show', 'edit', true)
|
||||
|
||||
->setField('index', 'Label', Field\TextField::class, [
|
||||
'property' => 'label',
|
||||
])
|
||||
->setField('index', 'Status', Field\TextField::class, [
|
||||
'view' => 'blog/project_admin/field/status.html.twig',
|
||||
'sort' => ['status', '.status'],
|
||||
'attr' => ['class' => 'miw-100'],
|
||||
])
|
||||
|
||||
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
|
||||
$manager->delete($entity);
|
||||
})
|
||||
->setBatchAction('index', 'draft', 'Statut : publier', function(EntityInterface $entity, EntityManager $manager) {
|
||||
$manager->update($entity->setStatus(Project::PUBLISHED));
|
||||
})
|
||||
->setBatchAction('index', 'publish', 'Statut : brouillon', function(EntityInterface $entity, EntityManager $manager) {
|
||||
$manager->update($entity->setStatus(Project::DRAFT));
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'project';
|
||||
}
|
||||
}
|
17
src/Controller/ProjectController.php
Normal file
17
src/Controller/ProjectController.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Core\Controller\Site\PageController;
|
||||
use App\Repository\ProjectRepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ProjectController extends PageController
|
||||
{
|
||||
public function projects(ProjectRepositoryQuery $query): Response
|
||||
{
|
||||
return $this->defaultRender($this->siteRequest->getPage()->getTemplate(), [
|
||||
'projects' => $query->create()->published()->ordered()->find(),
|
||||
]);
|
||||
}
|
||||
}
|
117
src/Entity/Project.php
Normal file
117
src/Entity/Project.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Core\Doctrine\Timestampable;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Repository\ProjectRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=ProjectRepository::class)
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*/
|
||||
class Project implements EntityInterface
|
||||
{
|
||||
use Timestampable;
|
||||
|
||||
const DRAFT = 0;
|
||||
const PUBLISHED = 1;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
private $label;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", options={"default"=0})
|
||||
*/
|
||||
private $status = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", nullable=true)
|
||||
*/
|
||||
private $sortOrder;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="array")
|
||||
*/
|
||||
private $links = [];
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(?string $description): self
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(int $status): self
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSortOrder(): ?int
|
||||
{
|
||||
return $this->sortOrder;
|
||||
}
|
||||
|
||||
public function setSortOrder(?int $sortOrder): self
|
||||
{
|
||||
$this->sortOrder = $sortOrder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLinks(): ?array
|
||||
{
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
public function setLinks(array $links): self
|
||||
{
|
||||
$this->links = $links;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
14
src/Factory/ProjectFactory.php
Normal file
14
src/Factory/ProjectFactory.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
use App\Entity\Project as Entity;
|
||||
|
||||
class ProjectFactory implements FactoryInterface
|
||||
{
|
||||
public function create(): Entity
|
||||
{
|
||||
return new Entity();
|
||||
}
|
||||
}
|
50
src/Form/ProjectLinkType.php
Normal file
50
src/Form/ProjectLinkType.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
|
||||
class ProjectLinkType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add(
|
||||
'label',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Libellé',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'url',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'URL',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
// Configure your form options here
|
||||
]);
|
||||
}
|
||||
}
|
82
src/Form/ProjectType.php
Normal file
82
src/Form/ProjectType.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Project;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use App\Form\Type\SimpleMdTextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use App\Core\Form\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
|
||||
class ProjectType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add(
|
||||
'label',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Libellé',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'status',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'label' => 'Statut',
|
||||
'required' => true,
|
||||
'choices' => [
|
||||
'Brouillon' => Project::DRAFT,
|
||||
'Publié' => Project::PUBLISHED,
|
||||
],
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'description',
|
||||
SimpleMdTextareaType::class,
|
||||
[
|
||||
'label' => 'Contenu',
|
||||
'required' => false,
|
||||
'constraints' => [
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'links',
|
||||
CollectionType::class,
|
||||
[
|
||||
'label' => 'Liens',
|
||||
'entry_type' => ProjectLinkType::class,
|
||||
'by_reference' => false,
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'prototype' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Project::class,
|
||||
]);
|
||||
}
|
||||
}
|
76
src/Repository/ProjectRepository.php
Normal file
76
src/Repository/ProjectRepository.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Project;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @method Project|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Project|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Project[] findAll()
|
||||
* @method Project[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class ProjectRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Project::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
public function add(Project $entity, bool $flush = true): void
|
||||
{
|
||||
$this->_em->persist($entity);
|
||||
if ($flush) {
|
||||
$this->_em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ORMException
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
public function remove(Project $entity, bool $flush = true): void
|
||||
{
|
||||
$this->_em->remove($entity);
|
||||
if ($flush) {
|
||||
$this->_em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Project[] Returns an array of Project objects
|
||||
// */
|
||||
/*
|
||||
public function findByExampleField($value)
|
||||
{
|
||||
return $this->createQueryBuilder('p')
|
||||
->andWhere('p.exampleField = :val')
|
||||
->setParameter('val', $value)
|
||||
->orderBy('p.id', 'ASC')
|
||||
->setMaxResults(10)
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public function findOneBySomeField($value): ?Project
|
||||
{
|
||||
return $this->createQueryBuilder('p')
|
||||
->andWhere('p.exampleField = :val')
|
||||
->setParameter('val', $value)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
}
|
||||
*/
|
||||
}
|
29
src/Repository/ProjectRepositoryQuery.php
Normal file
29
src/Repository/ProjectRepositoryQuery.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Core\Repository\RepositoryQuery;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use App\Repository\ProjectRepository as Repository;
|
||||
use App\Entity\Project;
|
||||
|
||||
class ProjectRepositoryQuery extends RepositoryQuery
|
||||
{
|
||||
public function __construct(Repository $repository, PaginatorInterface $paginator)
|
||||
{
|
||||
parent::__construct($repository, 'p', $paginator);
|
||||
}
|
||||
|
||||
public function published()
|
||||
{
|
||||
return $this
|
||||
->andWhere('.status = :published')
|
||||
->setParameter(':published', Project::PUBLISHED)
|
||||
;
|
||||
}
|
||||
|
||||
public function ordered()
|
||||
{
|
||||
return $this->orderBy('.sortOrder');
|
||||
}
|
||||
}
|
|
@ -25,12 +25,21 @@
|
|||
}) }}
|
||||
</ul>
|
||||
|
||||
{{ include('@Core/admin/module/_menu_section.html.twig', {label: 'Impression 3D'}) }}
|
||||
{{ include('@Core/admin/module/_menu_section.html.twig', {label: 'Projets'}) }}
|
||||
|
||||
<ul class="nav flex-column">
|
||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||
id: 'project',
|
||||
label: 'Projets',
|
||||
route: path('admin_project_index'),
|
||||
icon: 'fa fa-hat-wizard'
|
||||
}) }}
|
||||
</ul>
|
||||
|
||||
<ul class="nav flex-column">
|
||||
{{ include('@Core/admin/module/_menu_item.html.twig', {
|
||||
id: 'stl_mesh',
|
||||
label: 'Mesh',
|
||||
label: 'Modèles 3D',
|
||||
route: path('admin_stl_mesh_index'),
|
||||
icon: 'fa fa-pen'
|
||||
}) }}
|
||||
|
|
7
templates/blog/project_admin/field/status.html.twig
Normal file
7
templates/blog/project_admin/field/status.html.twig
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% set map = {
|
||||
0: ['warning', 'Brouillon'],
|
||||
1: ['success', 'Publié'],
|
||||
} %}
|
||||
<button class="btn btn-sm btn-{{ map[entity.status].0 }}">
|
||||
{{ map[entity.status].1 }}
|
||||
</button>
|
20
templates/blog/project_admin/field/title.html.twig
Normal file
20
templates/blog/project_admin/field/title.html.twig
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% if entity.image %}
|
||||
{% set image = asset(entity.image.pathname) %}
|
||||
{% else %}
|
||||
{% set image = asset('build/images/no-image.png') %}
|
||||
{% endif %}
|
||||
|
||||
<img src="{{ asset('build/images/blank.png') }}" style="background: url({{ image }}) center center; background-size: cover" class="rounded float-left mr-2">
|
||||
|
||||
<a href="{{ path('admin_blog_post_show', {entity: entity.id}) }}" class="font-weight-bold text-body d-block">
|
||||
{{ entity.title }}
|
||||
</a>
|
||||
|
||||
{% set categories = [] %}
|
||||
|
||||
{% for category in entity.categories %}
|
||||
{% set url = path('admin_blog_category_show', {entity: category.id}) %}
|
||||
{% set categories = categories|merge(['<a href="' ~ url ~ '">' ~ category.title ~ '</a>']) %}
|
||||
{% endfor %}
|
||||
|
||||
Dans {{ categories|join(', ')|raw }}
|
36
templates/page/simple/projects.html.twig
Normal file
36
templates/page/simple/projects.html.twig
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block page_title %}
|
||||
{{- _page.title.value -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="col-12">
|
||||
<div class="body">
|
||||
<div class="body-content">
|
||||
{{- _page.content.value|murph_url|markdown('post') -}}
|
||||
|
||||
|
||||
{% for project in projects %}
|
||||
<hr>
|
||||
|
||||
<h2>{{- project.label -}}</h2>
|
||||
|
||||
{{- project.description|murph_url|markdown('post') -}}
|
||||
|
||||
{% if project.links %}
|
||||
<ul class="list--inline">
|
||||
{% for link in project.links %}
|
||||
<li>
|
||||
<a class="button small" href="{{ link.url|murph_url }}" target="_blank">
|
||||
{{- link.label -}}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in a new issue