Commit initial

This commit is contained in:
Matt Marcha 2021-02-19 19:14:50 +01:00
commit 54a1e935ac
5 changed files with 484 additions and 0 deletions

111
Views/FiltersView.php Normal file
View file

@ -0,0 +1,111 @@
<?php
/**
* Class FiltersView
* Gère l'affichage des filtres de mesures
*/
class FiltersView {
public function __construct() {
add_shortcode('graph_mesures', __CLASS__.'::create_measures_filters');
/**
** Ajout des scripts et styles
**/
// TODO : Réfléchir à un moyen de n'inclure tout ce bouzin uniquement si nécessaire (quand filtres appelés)
add_action('wp_enqueue_scripts', __CLASS__.'::enqueue_filters_scripts', 20);
add_action( 'wp_enqueue_scripts', __CLASS__.'::enqueue_filters_style' );
}
static public function enqueue_filters_scripts() {
wp_register_script('filtersJS', plugin_dir_url(__FILE__) . 'inc/filters.js', array('jquery'),'1.1', true);
wp_enqueue_script('filtersJS');
}
static public function enqueue_filters_style() {
wp_enqueue_style( 'filters-style', plugin_dir_url(__FILE__) . 'inc/filters.css' );
}
/**
* Génère la liste des filtres pour les mesures affichées
*/
public static function create_measures_filters() {
// Total des articles
$total = wp_count_posts('post')->publish;
// Ensuite, pour chaque catégorie donnée, on va :
// récupérer le nombre de posts de cette cat
// récupérer la couleur associée
// calculer le pourcentage sur le total de mesures
// créer un bloc html avec la data correspondante
$stats = ["validated", "partially-validated", "discussed", "danger", "rejected", "undiscussed"];
$obj_total = 0;
$html = "";
// C'parti pour le cacamembert
$pie_chart = '<div class="pie">';
$labels = "<div class='home-legend-items'>";
foreach ($stats as $stat) {
// TODO : Sortir la requête du foreach et monter un modèle pour les taxonomies (pour embarquer la couleur)
$stat = get_term_by('slug', $stat, 'post-status');
$color = get_field('couleur', "category_" . $stat->term_id);
$percent = $stat->count * 100 / $total;
$huge = $percent > 50 ? 1 : 0;
static $offset = 0;
$pie_chart .= "<div class='pie__segment $stat->slug' status='$stat->slug'
style='--offset: $offset; --value: $percent; --bg: $color; --over50: $huge;'
filter-label='$stat->name'
filter-id='$stat->slug'>
<p class='label statusTotal $stat->slug'>$stat->count</p>
</div>";
$offset += $percent;
$labels .= "<div class='home-legend-item'>";
$labels .= "<div class='legend-color' style='background-color: $color;'></div>";
$labels .= "<p class='status-title'
status='". $stat->slug ."'
filter-label='$stat->name'
filter-id='$stat->slug'>
$stat->name ( <span class='statusTotal $stat->slug'>$stat->count</span> )</p>";
$labels .= "</div>";
if (in_array($stat->slug, ['validated', 'partially-validated'])) {
$obj_total += $stat->count;
}
}
$pie_chart .= '</div>';
$labels .= '</div>';
//ok, on monte les filtres par catégorie maintenant
$cats_title = "<p class='filters-cats-label'> Thématique </p>";
$cats = "<div class='filters-categories'>
<select name='cat' id='filters_cat_select' class='postform'>
<option value='' selected='selected'>Toutes les thématiques</option>";
foreach (get_categories(['parent' => 0]) as $the_cat) {
$cats .= "<option class='topic'
value='$the_cat->term_id'
filter-label='$the_cat->name'
filter-id='$the_cat->term_id'>
$the_cat->name</option>";
}
$cats .= "</select></div>";
return '<div id="measures-filters-container">' . $cats_title . $cats . $pie_chart . $labels . '</div>' ;
}
}

94
Views/inc/filters.css Normal file
View file

@ -0,0 +1,94 @@
/**
** Filters graph & selection
**/
/* Thematiques */
.filters-cats-label {
font-weight: 500;
margin-bottom: 0;
font-size: .8em;
}
.filters-categories select {
font-size: .6em;
margin-bottom: 1.5em;
}
/* Pie chart */
.pie {
border-radius: 100%;
height: calc(var(--size, 250) * 1px);
overflow: hidden;
position: relative;
width: calc(var(--size, 250) * 1px);
}
.pie__segment {
--a: calc(var(--over50, 0) * -100%);
--b: calc((1 + var(--over50, 0)) * 100%);
--degrees: calc((var(--offset, 0) / 100) * 360);
-webkit-clip-path: polygon(var(--a) var(--a), var(--b) var(--a), var(--b) var(--b), var(--a) var(--b));
clip-path: polygon(var(--a) var(--a), var(--b) var(--a), var(--b) var(--b), var(--a) var(--b));
height: 100%;
position: absolute;
transform: translate(0, -50%) rotate(90deg) rotate(calc(var(--degrees) * 1deg));
transform-origin: 50% 100%;
width: 100%;
z-index: calc(1 + var(--over50));
}
.pie__segment:after,
.pie__segment:before {
background: var(--bg, #e74c3c);
content: '';
height: 100%;
position: absolute;
width: 100%;
}
.pie__segment:before {
--degrees: calc((var(--value, 45) / 100) * 360);
transform: translate(0, 100%) rotate(calc(var(--degrees) * 1deg));
transform-origin: 50% 0%;
}
.pie__segment:after {
opacity: var(--over50, 0);
}
/* au cas où on en revienne aux labels dans les slices :*/
.pie__segment .label {
position: absolute;
bottom: -30px;
color: #fff;
z-index: 99;
transform: translate(0, -50%) rotate(-90deg) rotate(calc(var(--degrees) * -1deg));
left: 8px;
text-align: center;
font-size: .8em;
font-weight: 900;
}
/*** Legends ***/
.home-legend-items {
margin-top: 1em;
}
.home-legend-item {
position: relative;
padding-left: 30px;
}
.home-legend-item .legend-color {
position: absolute;
left: 0;
top: 0;
bottom: 0;
margin: auto;
width: 20px;
height: 20px;
}

225
Views/inc/filters.js Normal file
View file

@ -0,0 +1,225 @@
jQuery( document ).ready(function($) {
/** Filtrage des propositions **/
// On crée les var : boite à filtre, et un tableua pour stocker lesdits filtres
var filters_div = $('#filters-container');
var current_filters = new Object();
/**
* filter_actions
* ajoute ou supprime les filtres au tableau et la classe selected aux filtres, en fonction de l'élément (et de si c'est select ou statut)
*
*/
function filters_action(elem, currentFilters) {
//Si c'est le select qui a changé, on supprime les filtres par catégorie existants
if (elem.nodeName === 'OPTION') {
$('#filters_cat_select option').removeClass('active');
currentFilters = new Object();
$(filters_div).children().remove();
}
// Si la valeur est nulle on arrête tout
if (!elem.hasAttribute('filter-id')) {
$(filters_div).trigger("filters-changed", [currentFilters]);
changePie(elem, currentFilters);
return currentFilters;
}
//Sinon s'occupe du tableau et de la classe
var filter_id = elem.getAttribute('filter-id');
var filter_label = elem.getAttribute('filter-label');
// Si c'est un filtre de thématique qui a été supprimé, on réinitialise le select et les filtres
if ($(elem).hasClass('measure-filter') && !isNaN(parseInt(filter_id))) {
$('#filters_cat_select').prop('selectedIndex', 0);
// TODO : supprimer les autres filtres (status) si on supprime une catégorie
}
// Il y est déjà : on supprime le filtre
if (filter_id in currentFilters) {
//suppression du tableau
delete currentFilters[filter_id];
//suppression de la barre des filtres
$(filters_div).children('[filter-id="' + filter_id + '"]').remove();
}
// il y est pas : on l'ajoute
else {
//ajout dans le tableau
currentFilters[filter_id] = filter_label;
// Ajout dans la barre
var htmlFilter = "<div class='measure-filter' filter-id='" + filter_id + "'><p class='name'>" + filter_label + "</p><div class='filter-close'>+</div></div>";
filters_div.append(htmlFilter);
}
// on switche la classe
$('#measures-filters-container [filter-id="' + filter_id + '"]').toggleClass('active');
console.log($(elem).attr('filter-id'));
if(!isNaN(filter_id)){
changePie(elem, currentFilters);
}
// Enfin on déclenche l'event filters-change
$(filters_div).trigger("filters-changed", [currentFilters]);
// On renvoie la liste des filtres mise à jour;
return currentFilters;
}
// Au clic sur un élément (ou change du select), on appelle la fonction avec l'élément (id&label) en argument
//Les statuts
$(".home-legend-item .status-title, .pie .pie__segment").click(function(){
current_filters = filters_action(this, current_filters);
});
// les catégories
$("#filters_cat_select").change(function(){
var elem = $(this).children(':selected').get(0);
current_filters = filters_action(elem, current_filters);
// on change la pie
changePie(elem, current_filters);
});
// La barre de filtres
$("body").on("click", "div.filters-container .filter-close", function (){
elem = $(this).closest('.measure-filter').get(0);
current_filters = filters_action(elem, current_filters);
});
/**
* filters-changed
* Evènement gérant l'affichage/masquage des mesures selon les filtres
*/
$(filters_div).on("filters-changed", function(event, currentFilters) {
var measures = $("article.measure-wrapper");
// si aucun filtre on affiche tout
if ($.isEmptyObject(currentFilters)) {
measures.show();
return;
}
// On récupère les filtres triés
var filters = filtersSeparation(currentFilters);
// On filtre pour afficher/masquer ce qui correspond aux bonnes classe
measures.filter(filters[0] + filters[1].join(', ' + filters[0] )).show();
measures.not(filters[0] + filters[1].join(', ' + filters[0] )).hide();
});
/**
* filtersSeparation
* Spare les filtres en catégories et statuts
*/
function filtersSeparation(currentFilters){
// On parcourt le tableau actuel des filtres, et on récupère les ID
var idsToShow = Object.keys(currentFilters);
// séparer catégories et statut : si une valeur nupérique est présente dans les filtres, on la récupère et l'extrait.
var cat = '';
var status = [];
// On parcourt la liste des id et on sépare cat et stat
for (var i = 0; i < idsToShow.length; i++) {
// Si c'est numérique c'est une catégorie (AND)
if (!isNaN(parseInt(idsToShow[i]))) {
cat = '.' + idsToShow[i];
}
//sinon c'est un statut (OR)
else {
status.push('.' + idsToShow[i]);
}
}
return [cat, status];
}
/**
* ChangePie
* Met à jour le pie chart à partir de l'option selectionnée
*/
function changePie(elem, currentFilters) {
var container = $('div#measures-filters-container');
var measures = $('div#measures-container');
// On récupère les filtres triés
var filters = filtersSeparation(currentFilters);
// C'est parti pour les calculs !
// Pour chaque statut, on calcule en fonction des filtres
var statusNames = ["validated", 'partially-validated', "discussed", "danger", "rejected", "undiscussed"];
//total des mesures
var counts = {'total': measures.children('.measure-wrapper' + filters[0]).length};
var offset = 0;
for (var i = 0; i < statusNames.length; i++) {
// on compte
counts[statusNames[i]] = measures.children('.measure-wrapper' + filters[0] + '.' + statusNames[i]).length;
var percent = counts[statusNames[i]] * 100 / counts['total'];
var huge = percent > 50 ? 1 : 0;
// on change les custom val
container.find('.pie .pie__segment.' + statusNames[i]).css('--value', percent);
container.find('.pie .pie__segment.' + statusNames[i]).css('--offset', offset);
container.find('.pie .pie__segment.' + statusNames[i]).css('--over50', huge);
container.find('.statusTotal.' + statusNames[i]).text(counts[statusNames[i]]);
//mise à jour de l'offset
offset = offset + percent;
}
}
/**
* Recherche de type texte
*
*/
var css_selector = ".measure-wrapper, .measure-wrapper";
/* Par recherche texte */
function clearSearchField() {
$(".search-container #measure-search").val("");
}
$(".search-container #measure-search").keyup(function() {
// récupération du query actuel
var query = $.trim($(this).val());
if (query === "") { // si il est vide on affiche tout
// on affiche toutes les propositions, en tenant compte des filtres éventuels
$(css_selector).show();
}
else {
// on va chercher les mentions de ce query dans les mesures et on cache celles qui ne l'ont pas
$(css_selector).show().not(':Contains(' + query + ')').hide();
}
});
$(".search-container #measure-search-reset").click(function () {
clearSearchField();
$(".search-container #measure-search").trigger("keyup");
});
});

52
cent-filtres.php Normal file
View file

@ -0,0 +1,52 @@
<?php
/*
* Plugin Name: Cent Filtres
* Description: Plugin permettant l'affichage des mesures de la CCC selon des filtres
* Plugin URI: https://sansfiltre.les150.fr
* Version : 0.1
* Requires at least: 5.6
* Requires PHP: 7.4
* Author: Matt Marcha
* Author URI: https://matt.marcha.pro
*/
/**
* Options
* Si les mettre éditable en admin n'est pas nécessaire pour l'instant
*/
// Choisir le hook pour temporaliser l'initialisation de l'autoloader (si utres plugins pré-requis par exemple)
add_action('init', 'spclInitAutoLoader');
/*
* Autoloader
*/
function spclInitAutoLoader() {
// La liste des dossier à parcourir
// Par défaut les plus courants
$spcls_folders = [
//"Models",
//"Controllers",
"Views"
];
foreach ($spcls_folders as $folder) {
// dans chaque, on inclut chaque fichier php
foreach (glob(plugin_dir_path( __FILE__ ). "$folder/*.php") as $filename) {
include_once "$filename";
/* instanciation des classes statiques de hook et d'outils */
// on précise la liste des dossiers contenant les classes à instancier directement
if (in_array($folder, [
//"Models",
//"Controllers",
"Views"
])) {
// On extrait la classe: il faut récupérer ce qu'il y a après le dernier slash et virer l'extension
$class = explode(".", array_slice(explode("/", $filename), -1, 1)[0])[0];
//et on instancie
new $class();
}
}
}
}

2
index.php Normal file
View file

@ -0,0 +1,2 @@
<?php
# Silence is golden.