Init project
This commit is contained in:
23
assets/controllers/news/delete_controller.js
Normal file
23
assets/controllers/news/delete_controller.js
Normal 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");
|
||||
}
|
||||
}
|
||||
165
assets/controllers/news/edit_image_selector_controller.js
Normal file
165
assets/controllers/news/edit_image_selector_controller.js
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
126
assets/controllers/news/image_selector_controller.js
Normal file
126
assets/controllers/news/image_selector_controller.js
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
33
assets/controllers/news/sortable_controller.js
Normal file
33
assets/controllers/news/sortable_controller.js
Normal 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 }),
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user