Skip to content
Snippets Groups Projects
Commit b6f93fd2 authored by Pierre Fleutot's avatar Pierre Fleutot
Browse files

Page entrée : tabs pour naviguer d'un lexique à l'autre et copier/supprimer...

Page entrée : tabs pour naviguer d'un lexique à l'autre et copier/supprimer l'entrée vers un autre lexique. Intégration panneau de gestion des labels.
parent 9fdef4d6
No related branches found
No related tags found
No related merge requests found
......@@ -4,6 +4,7 @@ twig:
globals:
languages: '@App\Manager\LanguagesManager'
label_manager: '@App\Manager\LabelManager'
lexicon_manager: '@App\Manager\LexiconManager'
when@test:
twig:
......
......@@ -155,7 +155,11 @@ table.label-table > tbody > tr > td {
.bg-definition {
background-color: #D6EADA;
}
.badge.badge-milestone {line-height: 1.3}
.badge.badge-milestone {
line-height: 1.3;
padding-top: 0;
padding-bottom: 0;
}
.card-body.card-definition {
margin-top: 2px;
......@@ -171,4 +175,37 @@ table.label-table > tbody > tr > td {
.faded {
opacity: 0.5;
}
.ajax-link {
cursor: pointer;
}
.nav-tabs .nav-link.tab-pink {
background-color: #FDF6F6;
}
.nav-tabs .nav-link.tab-dark-pink {
background-color: #FFE4E4;
}
.nav-tabs .nav-link.tab-grey {
background-color: #E4E4E4;
color: grey;
}
.nav-tabs .nav-link {
color: black;
border-bottom: 0;
}
.nav-tabs a.nav-link.active {
font-weight: bold;
}
#tabContent {
background-color: #FDF6F6;
padding: 15px;
}
#tabContent.tab-wiktionnary {
background-color: #FFE4E4;
}
#tableLabels.table > tbody > tr > td {
height: 52px;
}
\ No newline at end of file
......@@ -326,45 +326,61 @@ function initializeFilterFormFieldsBackground() {
function initializeAjaxLinks() {
$('body').on('click', '.ajax-link', function () {
var data;
var $overlay = $('#overlay').show();
var $target = $(this).closest('.blink-target').length ? $(this).closest('.blink-target') : $(this);
$target.addClass('faded');
var method = $(this).data('method');
// on récupère le json
if ($(this).data('json')) {
data = JSON.stringify($(this).data('json'));
} else {
data = $('#'+$(this).data('json-dynamic')).val();
}
if (!method || method.toUpperCase() === 'GET') {
$.ajax({
type: $(this).data('method'),
url: $(this).data('url'),
contentType: "application/json",
dataType: "json",
data: data,
processData: false,
success: function(response, textStatus, xhr) {
// console.log(response);
location.reload();
},
complete: function () {
$overlay.hide();
// $target = $target.removeClass('faded');
},
error: function (jqXHR, textStatus, errorThrown) {
// console.log(jqXHR.responseText, textStatus, errorThrown);
$target = $target.removeClass('faded');
var message = JSON.parse(jqXHR.responseText);
if ($('#bootstrap-modal').is(':visible')) {
$('#bootstrap-modal').find('.ajax-message').html('<div class="alert alert-danger">' + message.error + '</div>')
} else {
$('.ajax-message').html('<div class="alert alert-danger">' + message.error + '</div>')
$.ajax({
type: 'GET',
url: $(this).data('url'),
dataType: "json",
success: function (response, textStatus, xhr) {
location.reload();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.responseText, textStatus, errorThrown);
$overlay.hide();
}
$overlay.hide();
})
} else {
var data;
var $target = $(this).closest('.blink-target').length ? $(this).closest('.blink-target') : $(this);
$target.addClass('faded');
// on récupère le json
if ($(this).data('json')) {
data = JSON.stringify($(this).data('json'));
} else {
data = $('#' + $(this).data('json-dynamic')).val();
}
})
$.ajax({
type: method,
url: $(this).data('url'),
contentType: "application/json",
dataType: "json",
data: data,
processData: false,
success: function (response, textStatus, xhr) {
// console.log(response);
location.reload();
},
error: function (jqXHR, textStatus, errorThrown) {
// console.log(jqXHR.responseText, textStatus, errorThrown);
$target = $target.removeClass('faded');
var message = JSON.parse(jqXHR.responseText);
if ($('#bootstrap-modal').is(':visible')) {
$('#bootstrap-modal').find('.ajax-message').html('<div class="alert alert-danger">' + message.error + '</div>')
} else {
$('.ajax-message').html('<div class="alert alert-danger">' + message.error + '</div>')
}
$overlay.hide();
}
})
}
})
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ namespace App\Controller;
use App\Entity\Entry;
use App\Entity\Label;
use App\Entity\Lexicon;
use App\Entity\Log;
use App\Form\SearchStringType;
use App\Manager\LabelManager;
......@@ -22,55 +23,15 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
class EntryController extends AppBaseController
{
/**
* @Route("/{id}", name="app_entry_show", methods={"GET"})
* @Route("/{id}/show", name="app_entry_show", requirements={"id" = "\d+"})
*/
public function show(ManagerRegistry $doctrine, Request $request, Entry $entry): Response
public function show(LabelManager $labelManager, Request $request, Entry $entry)
{
$em = $doctrine->getManager();
$sortingColumn = null;
$sortingOrder = null;
$this->getSortingParameters($request, $sortingColumn, $sortingOrder, 'entry_show');
$form = $this->createForm(SearchStringType::class,null, array('method' => 'GET'));
$form->handleRequest($request);
$filter = $form->getData();
$filter['entry'] = $entry;
// Entrées triées par createdAt
// On ajoute l'ordre d'ajout de l'entrée (on se base sur createdAt) comme index
$entriesOrdered = $em->getRepository(Entry::class)->filter($filter, 'createdAt', 'ASC');
foreach ($entriesOrdered as $key => $entryOrdered) {
$entryOrdered->setAddingOrder($key);
}
// On ajoute l'id de l'entrée en index ( => tableau assciatif 'id' => $entry (avec colonne addingOrder)
$entriesOrderedIndexedById = [];
foreach ($entriesOrdered as $entryOrdered) {
$entriesOrderedIndexedById[$entryOrdered->getId()] = $entryOrdered;
}
// Entrées résultat cherché (triées avec le filtre sortingColumn)
// On ajoute l'id de l'entrée en index ( => tableau assciatif 'id' => $entry (avec colonne addingOrder)
$entries = $em->getRepository(Entry::class)->filter($filter, $sortingColumn, $sortingOrder);
$entriesIndexedById = [];
foreach ($entries as $entry) {
$entriesIndexedById[$entry->getId()] = $entry;
}
// On ajoute l'ordre d'ajout de l'entrée à partir du premier tableau
foreach ($entriesIndexedById as $id => $entryIndexedById) {
$entryIndexedById->setAddingOrder($entriesOrderedIndexedById[$id]->getAddingOrder());
}
// $entriesWithAddingOrderIndex = $entriesOrdered;
// usort($entriesWithAddingOrderIndex, function ($a, $b) { return $b->getCreatedAt()->getTimestamp() - $a->getCreatedAt()->getTimestamp(); });
return $this->render('entry/show.html.twig', [
'entries' => $entriesIndexedById,
'entry' => $entry,
'form' => $form->createView(),
'sortingColumn' => $sortingColumn,
'sortingOrder' => $sortingOrder,
]);
return $this->render('entry/show.html.twig', array(
'entry' => $entry,
'wiktionnaryLexicon' => false,
));
}
/**
......@@ -88,6 +49,50 @@ class EntryController extends AppBaseController
));
}
/**
* @Route("/{id}/copy/{lexiconId}", name="app_entry_copy", requirements={"id" = "\d+"})
* @ParamConverter("entry", options={"id" = "id"})
* @ParamConverter("lexicon", options={"id" = "lexiconId"})
*/
public function copyEntry(Request $request, Entry $entry, Lexicon $lexicon)
{
$forwardRequest = Request::create(
$this->generateUrl('api_copy_entries'), 'POST', [], [], [], [],
json_encode([
'entries' => [$entry->getId()],
'merge' => Lexicon::MERGE_OVERWRITE,
'target_lex' => [$lexicon->getId()],
])
);
$response = $this->forward('App\Controller\ApiEntryController::copyEntries', array(
'request' => $forwardRequest,
// '_route' => $request->attributes->get('_route'),
// '_route_params' => $request->attributes->get('_route_params'),
));
if ($response->getStatusCode() == 200) {
$this->addFlash('success', "Copie effectuée");
} else {
$message = (array) json_decode($response->getContent());
$this->addFlash('danger', reset($message) ?? 'Requête terminée');
}
return $this->redirectToRoute('app_entry_show', ['id' => $entry->getId()]);
}
/**
* @Route("/{id}/delete", name="app_entry_delete", requirements={"id" = "\d+"})
*/
public function deleteEntry(Request $request, Entry $entry)
{
$this->em->remove($entry);
$this->em->flush();
$this->addFlash('success', sprintf("L'entrée a été supprimée du lexique %s", $entry->getLexicon()));
return $this->redirect($request->get('backUrl') ? : $this->generateUrl('app_lexicon_show', ['id' => $entry->getLexicon()->getId()]));
}
/**
* PAS UTILISÉ
*
......
......@@ -15,6 +15,7 @@ use App\Repository\HeadwordRepository;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
......@@ -63,7 +64,7 @@ class HeadwordController extends AppBaseController
$this->em->flush();
return $this->redirectToRoute('app_lexicon_show', ['id' => $request->get('lexiconId')]);
return new JsonResponse();
}
}
......@@ -133,7 +133,7 @@ class LexiconController extends AppBaseController
/**
* @Route("/{id}/copy-entries", name="app_lexicon_copy_entries")
*/
public function copySelection(LabelManager $labelManager, Request $request, Lexicon $lexicon, EntryRepository $entryRepository)
public function copySelection(Request $request, Lexicon $lexicon)
{
$formData = $request->get('form');
$selectedIds = $formData['selected_entries'] ?? null;
......
<?php
namespace App\Controller;
use App\Entity\Entry;
use App\Entity\Label;
use App\Entity\Log;
use App\Form\SearchStringType;
use App\Manager\LabelManager;
use App\Repository\EntryRepository;
use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @Route("/wiktionnary")
*/
class WiktionnaryController extends AppBaseController
{
/**
* @Route("/{id}/show", name="app_wiktionnary_show", requirements={"id" = "\d+"})
*/
public function show(LabelManager $labelManager, Request $request, Entry $entry)
{
return $this->render('entry/show.html.twig', array(
'entry' => $entry,
));
}
}
......@@ -201,6 +201,20 @@ class Lexicon
}
}
/**
* @param Headword $headword
* @return Entry|mixed|null
*/
public function getEntryForHeadword(Headword $headword)
{
foreach ($this->getEntries() as $entry) {
if ($entry->getHeadword() === $headword) {
return $entry;
}
}
return null;
}
public function getId(): ?int
{
return $this->id;
......
......@@ -93,6 +93,26 @@
</div>
</div>
{# CONFIRM MODAL #}
<div id="confirm-dialog" class="modal fade " tabindex="-1" role="dialog" aria-hidden="true" data-keyboard="false"
data-backdrop="static" data-show-on-load="{{ showConfirmDialog|default(false) }}">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="bootstrap-modal-title">{{ 'Confirmation'|trans }}</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="confirm-dialog-message" class="pb-5">{{ confirmMessage|default('')|raw }}</div>
<div class="text-center">
<a class="btn btn-danger btn-ok" href="{{ confirmUrl|default('#') }}">{{ 'Confirmer'|trans }}</a>
<button type="button" class="btn btn-light" data-bs-dismiss="modal" aria-label="Close">{{ "Annuler"|trans }}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<table id="tableLabels" class="table table-bordered">
<tbody>
<tr>
<td class="col-md-2 modal-form" data-url="{{ path('app_entry_choose_label', {id: entry.id, category: constant("App\\Entity\\Label::LABEL_CATEGORY_GENERAL")}) }}">
{% for label in entry.headword.generalLabels %}<span class="blink-target">{% include "label/_labelBadge.html.twig" %}</span>{% endfor %}
</td>
</tr>
<tr>
<td class="col-md-2 modal-form" data-url="{{ path('app_entry_choose_label', {id: entry.id, category: constant("App\\Entity\\Label::LABEL_CATEGORY_INSTITUTIONAL")}) }}">
{% for label in entry.headword.institutionalLabels %}<span class="blink-target">{% include "label/_labelBadge.html.twig" %}</span>{% endfor %}
</td>
</tr>
<tr>
<td class="col-md-2 modal-form" data-url="{{ path('app_entry_choose_label', {id: entry.id, category: constant("App\\Entity\\Label::LABEL_CATEGORY_MILESTONE")}) }}">
{% for label in entry.headword.milestoneLabels %}<span class="blink-target">{% include "label/_labelBadge.html.twig" %}</span>{% endfor %}
</td>
</tr>
</tbody>
</table>
{% extends 'base.html.twig' %}
{% import 'macros.html.twig' as macros %}
{% block container %}container-fluid{% endblock %}
{% block title %}{{ entry|capitalize }}{% endblock %}
{% block body %}
<div class="row justify-content-center m-lg-5 m-sm-3">
<div class="col-md-12">
<h1 class="">
{% if not wiktionnaryLexicon %}
<a href="{{ path('app_lexicon_show', {id: entry.lexicon.id}) }}" class="btn btn-dark"><i class="bi bi-arrow-90deg-left"></i></a>
{% endif %}
{{ entry|capitalize }}
{% set known = entry.headword.knownByUser(app.user) %}
<a title="{{ known ? 'Mot-vedette connu. Cliquer pour modifier'|trans : 'Mot-vedette non connu. Cliquer pour modifier'|trans }}" href="#"
class="ajax-link" data-method="GET" data-url="{{ path('app_headword_toggle_known', {id: entry.headword.id, userId: app.user.id, lexiconId: entry.lexicon.id}) }}">
{% if known %}<i class="fa fa-circle text-success"></i>{% else %}<i class="fa fa-circle text-warning"></i>{% endif %}
</a>
</h1>
<ul class="nav nav-tabs mt-3">
{% for lexicon in app.user.myLexicons %}
{% set entryWithSameHeadwordInThisLexicon = lexicon.getEntryForHeadword(entry.headword) %}
<li class="nav-item dropdown">
<a class="nav-link {{ lexicon == entry.lexicon ? 'active' }} {{ entryWithSameHeadwordInThisLexicon ? 'tab-pink' : 'tab-grey' }} dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">{{ lexicon }}</a>
<ul class="dropdown-menu">
{% if entryWithSameHeadwordInThisLexicon %}
{% if lexicon != entry.lexicon %}
<li><a class="dropdown-item" href="{{ path('app_entry_show', {id: entryWithSameHeadwordInThisLexicon.id}) }}">{{ "Voir l'entrée dans ce lexique"|trans }}</a></li>
{% endif %}
{% set backUrl = (lexicon == entry.lexicon ? path('app_lexicon_show', {id: entry.lexicon.id}) : path('app_entry_show', {id: entry.id})) %}
<li><a class="dropdown-item" href="#"
data-href="{{ path('app_entry_delete', {id: entryWithSameHeadwordInThisLexicon.id, backUrl: backUrl}) }}"
data-confirm="{{ "Confirmer la suppression ?"|trans }}" data-bs-toggle="modal" data-bs-target="#confirm-dialog">
{{ "Supprimer l'entrée dans ce lexique"|trans }}
</a></li>
{% endif %}
{% if lexicon != entry.lexicon %}
{% if entryWithSameHeadwordInThisLexicon %}
<li><a class="dropdown-item" href="#" data-href="{{ path('app_entry_copy', {id: entry.id, lexiconId: lexicon.id}) }}"
data-confirm="{{ "Confirmer la copie ? Une entrée similaire existe déjà dans le lexique que vous avez sélectionné. Si vous copiez celle-ci, le contenu de l'autre sera perdue"|trans }}" data-bs-toggle="modal" data-bs-target="#confirm-dialog">
{{ "Copier l'entrée dans ce lexique"|trans }}
</a></li>
{% else %}
<li><a class="dropdown-item" href="{{ path('app_entry_copy', {id: entry.id, lexiconId: lexicon.id}) }}">{{ "Copier l'entrée dans ce lexique"|trans }}</a></li>
{% endif %}
{% endif %}
</ul>
</li>
{% endfor %}
{% if not entry.lexicon.isNewWords %}
<li class="nav-item">
<a class="nav-link tab-dark-pink" href="#">{{ "Wiktionnaire"|trans }}</a>
</li>
{% endif %}
</ul>
<div id="tabContent" class="{{ wiktionnaryLexicon ? 'tab-wiktionnary' }}">
<div class="row">
<div class="col-sm-6">
{% include "entry/_entryLabels.html.twig" %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
......@@ -66,14 +66,14 @@
<tr class="{{ known ? 'headword-known' }}">
<td>{{entry.addingOrder }}</td>
<td class="d-flex justify-content-between">
<label>
<a href="{{ path('app_entry_show', {id: entry.id}) }}">
{# "value" est utilisé par Symfo quand on soumet le form. "data-headword-id" est utilisé par la vue chooseLabel pour injecter du json dans le sliens ajax #}
<input class="me-2" type="checkbox" name="form[selected_entries][]" data-headword-id="{{ entry.headword.id }}"
value="{{ entry.id }}" id="form_selected_entries_{{ entry.id }}"/>
{{ entry }}
</label>
<a title="{{ known ? 'Mot-vedette connu. Cliquer pour modifier'|trans : 'Mot-vedette connu. Cliquer pour modifier'|trans }}"
href="{{ path('app_headword_toggle_known', {id: entry.headword.id, userId: app.user.id, lexiconId: lexicon.id}) }}">
</a>
<a title="{{ known ? 'Mot-vedette connu. Cliquer pour modifier'|trans : 'Mot-vedette non connu. Cliquer pour modifier'|trans }}"
href="#" class="ajax-link" data-method="GET" data-url="{{ path('app_headword_toggle_known', {id: entry.headword.id, userId: app.user.id, lexiconId: lexicon.id}) }}">
{% if known %}<i class="fa fa-circle text-success"></i>{% else %}<i class="fa fa-circle text-warning"></i>{% endif %}
</a>
</td>
......
......@@ -6,6 +6,11 @@
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="{{ "Rechercher"|trans }}" aria-label="Search">
<button class="btn btn-light me-3" type="submit"><i class="fa fa-search"></i> </button>
</form>
{% if app.user %}
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment