Init project

This commit is contained in:
2026-01-11 16:19:42 +01:00
commit df59325836
380 changed files with 33805 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["dialog", "form", "token"];
open(event) {
const button = event.currentTarget;
const url = button.dataset.url;
const token = button.dataset.token;
// Remplit le formulaire avec les bonnes données
this.formTarget.action = url;
this.tokenTarget.value = token;
this.dialogTarget.classList.remove("hidden");
this.dialogTarget.classList.add("flex");
}
close() {
this.dialogTarget.classList.add("hidden");
this.dialogTarget.classList.remove("flex");
}
}

View File

@@ -0,0 +1,165 @@
import { Controller } from "@hotwired/stimulus";
import Sortable from "sortablejs";
export default class extends Controller {
static targets = [
"input",
"preview",
"list",
"card",
"checkbox",
"checkIcon",
"overlay",
"count",
];
sortable = null;
connect() {
this.restoreInitialSelection();
this.enableSortable();
this.updateCount();
}
// ---------------------------------------------------------------------
// 1) Restaurer auto la sélection existante (images déjà liées)
// ---------------------------------------------------------------------
restoreInitialSelection() {
this.previewTarget.innerHTML = "";
const selectedIds = [
...document.querySelectorAll('input[name="selectedImages[]"]'),
].map((input) => input.value);
// coche les bonnes cases dans la modale
this.checkboxTargets.forEach((checkbox) => {
if (selectedIds.includes(checkbox.value)) {
checkbox.checked = true;
this.updateCard(checkbox.closest("label"), true);
}
});
// Ajoute dans le preview
selectedIds.forEach((id) => {
const card = this.cardTargets.find((c) => c.dataset.id === id);
if (!card) return;
const img = document.createElement("img");
img.src = card.dataset.url;
img.dataset.id = id;
img.className = "w-20 h-20 rounded object-cover cursor-move";
this.previewTarget.appendChild(img);
});
}
// ---------------------------------------------------------------------
// 2) Sélection dans la modale
// ---------------------------------------------------------------------
cardClick(event) {
const card = event.currentTarget;
const checkbox = card.querySelector("input[type='checkbox']");
checkbox.checked = !checkbox.checked;
this.updateCard(card, checkbox.checked);
this.updateCount();
}
toggleCheckbox(event) {
const checkbox = event.currentTarget;
const card = checkbox.closest("label");
this.updateCard(card, checkbox.checked);
this.updateCount();
}
updateCard(card, checked) {
const icon = card.querySelector(
"[data-news--edit-image-selector-target='checkIcon']",
);
const overlay = card.querySelector(
"[data-news--edit-image-selector-target='overlay']",
);
if (icon) icon.classList.toggle("hidden", !checked);
if (overlay) overlay.classList.toggle("hidden", !checked);
card.classList.toggle("ring-4", checked);
card.classList.toggle("ring-amber-500", checked);
}
updateCount() {
const total = this.checkboxTargets.filter((c) => c.checked).length;
if (this.hasCountTarget) {
this.countTarget.textContent = `${total} sélectionnée(s)`;
}
}
// ---------------------------------------------------------------------
// 3) Validation depuis la modale → update preview + ordre
// ---------------------------------------------------------------------
validate() {
const selected = this.checkboxTargets
.filter((c) => c.checked)
.map((c) => c.value);
// 🔥 RESET COMPLET
this.previewTarget.innerHTML = "";
// 🔁 RECONSTRUCTION PROPRE
selected.forEach((id) => {
const card = this.cardTargets.find((c) => c.dataset.id === id);
if (!card) return;
const img = document.createElement("img");
img.src = card.dataset.url;
img.dataset.id = id;
img.className = "w-20 h-20 rounded object-cover cursor-move";
this.previewTarget.appendChild(img);
});
this.enableSortable();
this.updateOrder();
document
.querySelector("[command='close'][commandfor='edit-news-image-dialog']")
.click();
}
// ---------------------------------------------------------------------
// 4) Drag & drop
// ---------------------------------------------------------------------
enableSortable() {
if (this.sortable) this.sortable.destroy();
this.sortable = Sortable.create(this.previewTarget, {
animation: 150,
ghostClass: "opacity-40",
onSort: () => this.updateOrder(),
});
}
updateOrder() {
const form = this.previewTarget.closest("form");
// supprime uniquement les inputs dynamiques
form
.querySelectorAll(
'input[name="selectedImages[]"]:not([data-permanent="true"])',
)
.forEach((i) => i.remove());
// recréer les inputs cachés
Array.from(this.previewTarget.children).forEach((img) => {
const hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = "selectedImages[]";
hidden.value = img.dataset.id;
form.appendChild(hidden);
});
}
}

View File

@@ -0,0 +1,126 @@
import { Controller } from "@hotwired/stimulus";
import Sortable from "sortablejs";
export default class extends Controller {
static targets = [
"container",
"input",
"preview",
"list",
"card",
"checkbox",
"checkIcon",
"count",
];
sortable = null;
// ------------------------------------------------------------
// Sélection normale
// ------------------------------------------------------------
cardClick(event) {
const card = event.currentTarget;
const checkbox = card.querySelector("input[type='checkbox']");
checkbox.checked = !checkbox.checked;
this.updateCard(card, checkbox.checked);
this.updateCount();
}
toggleCheckbox(event) {
const checkbox = event.currentTarget;
const card = checkbox.closest("label");
this.updateCard(card, checkbox.checked);
this.updateCount();
}
updateCard(card, checked) {
const icon = card.querySelector(
"[data-news--image-selector-target='checkIcon']",
);
const overlay = card.querySelector(
"[data-news--image-selector-target='overlay']",
);
icon.classList.toggle("hidden", !checked);
overlay.classList.toggle("hidden", !checked);
card.classList.toggle("ring-4", checked);
card.classList.toggle("ring-amber-500", checked);
}
updateCount() {
const total = this.checkboxTargets.filter((c) => c.checked).length;
if (this.hasCountTarget) {
this.countTarget.textContent = `${total} sélectionnée(s)`;
}
}
// ------------------------------------------------------------
// Validation → création du preview + activation du drag&drop
// ------------------------------------------------------------
validate() {
const selected = this.checkboxTargets
.filter((c) => c.checked)
.map((c) => c.value);
// 🔥 RESET TOTAL DU PREVIEW
this.previewTarget.innerHTML = "";
// 🔁 RECONSTRUCTION À PARTIR DE LA SOURCE DE VÉRITÉ
selected.forEach((id) => {
const card = this.cardTargets.find((c) => c.dataset.id === id);
if (!card) return;
const img = document.createElement("img");
img.src = card.dataset.url;
img.dataset.id = id;
img.className = "w-20 h-20 rounded object-cover cursor-move";
this.previewTarget.appendChild(img);
});
this.enableSortable();
this.updateOrder();
// ferme la modale
document.querySelector("[command='close'][commandfor='dialog']").click();
}
// ------------------------------------------------------------
// SortableJS (drag & drop)
// ------------------------------------------------------------
enableSortable() {
if (this.sortable) {
this.sortable.destroy(); // reset si déjà actif
}
this.sortable = Sortable.create(this.previewTarget, {
animation: 150,
ghostClass: "opacity-40",
onSort: () => {
this.updateOrder();
},
});
}
updateOrder() {
// Supprime les anciennes valeurs
const container = this.previewTarget.closest("form");
container
.querySelectorAll('input[name="selectedImages[]"]')
.forEach((i) => i.remove());
// Crée un input caché par image dans l'ordre
Array.from(this.previewTarget.children).forEach((img) => {
const hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = "selectedImages[]";
hidden.value = img.dataset.id;
container.appendChild(hidden);
});
}
}

View File

@@ -0,0 +1,33 @@
import Sortable from "sortablejs";
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static values = {
url: String,
};
static targets = ["item"];
connect() {
this.sortable = new Sortable(this.element, {
animation: 150,
handle: '[data-news--sortable-target="handle"]',
onEnd: this.reorder.bind(this),
});
}
reorder() {
const order = this.itemTargets.map((el, index) => ({
id: el.dataset.id,
position: index + 1,
}));
fetch(this.urlValue, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ order }),
});
}
}