mise en place de la partie administration
This commit is contained in:
parent
a24ba28630
commit
ef73c36ee3
270
admin.php
Normal file
270
admin.php
Normal file
@ -0,0 +1,270 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Vérifier si un utilisateur est connecté
|
||||
function checkAuth() {
|
||||
if (!isset($_SESSION['admin_id'])) {
|
||||
header('Location: admin.php?action=login');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Se connecter à la base de données
|
||||
function getDB() {
|
||||
return new SQLite3('database.sqlite');
|
||||
}
|
||||
|
||||
// Page de connexion
|
||||
function showLoginForm($error = null) {
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Connexion - ICO</title>
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="styles-admin.css">
|
||||
</head>
|
||||
<body class="admin-page">
|
||||
<div class="admin-login">
|
||||
<h1>Connexion</h1>
|
||||
<?php if ($error): ?>
|
||||
<div class="error-message"><?php echo htmlspecialchars($error); ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="post" action="admin.php?action=login">
|
||||
<div class="form-group">
|
||||
<label for="username">Identifiant :</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Mot de passe :</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="action-button">Se connecter</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Page principale d'administration
|
||||
function showAdminInterface() {
|
||||
checkAuth();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Administration - ICO</title>
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="styles-admin.css">
|
||||
</head>
|
||||
<body class="admin-page">
|
||||
<div class="admin-header">
|
||||
<h1>Administration ICO</h1>
|
||||
<div class="admin-actions">
|
||||
<a href="index.php" target="_blank" class="action-button action-button-success">Accéder à la galerie</a>
|
||||
<a href="admin.php?action=show_change_password" class="action-button">Changer le mot de passe</a>
|
||||
<a href="admin.php?action=logout" class="action-button action-button-danger">Déconnexion</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-content">
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="message success-message"><?php echo htmlspecialchars($_SESSION['success_message']); ?></div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="message error-message"><?php echo htmlspecialchars($_SESSION['error_message']); ?></div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="admin-menu">
|
||||
<a href="arbre.php" class="admin-menu-item">
|
||||
<div class="menu-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
||||
<path d="M9 13h6"></path>
|
||||
<path d="M12 10v6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="menu-content">
|
||||
<h2>Gestion des albums</h2>
|
||||
<p>Organisez vos albums et gérez l'arborescence de votre galerie photo.</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Page de changement de mot de passe
|
||||
function showChangePasswordForm() {
|
||||
checkAuth();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Changer le mot de passe - ICO</title>
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="styles-admin.css">
|
||||
</head>
|
||||
<body class="admin-page">
|
||||
<div class="admin-header">
|
||||
<h1>Changer le mot de passe</h1>
|
||||
<a href="admin.php" class="action-button action-button-secondary">Retour</a>
|
||||
</div>
|
||||
<div class="admin-content">
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="message error-message"><?php echo htmlspecialchars($_SESSION['error_message']); ?></div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="admin.php?action=change_password" class="form-container">
|
||||
<div class="form-group">
|
||||
<label for="current_password">Mot de passe actuel :</label>
|
||||
<input type="password" id="current_password" name="current_password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_password">Nouveau mot de passe :</label>
|
||||
<input type="password" id="new_password" name="new_password" required minlength="8">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirmer le mot de passe :</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required minlength="8">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="action-button">Changer le mot de passe</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Traiter la connexion
|
||||
function handleLogin() {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
$db = getDB();
|
||||
$stmt = $db->prepare('SELECT id, password_hash FROM admins WHERE username = :username');
|
||||
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
|
||||
$result = $stmt->execute();
|
||||
|
||||
if ($user = $result->fetchArray()) {
|
||||
if (password_verify($password, $user['password_hash'])) {
|
||||
$_SESSION['admin_id'] = $user['id'];
|
||||
header('Location: admin.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
showLoginForm('Identifiants incorrects');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoginForm();
|
||||
}
|
||||
|
||||
// Gérer le changement de mot de passe
|
||||
function handlePasswordChange() {
|
||||
checkAuth();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: admin.php');
|
||||
return;
|
||||
}
|
||||
|
||||
$currentPassword = $_POST['current_password'] ?? '';
|
||||
$newPassword = $_POST['new_password'] ?? '';
|
||||
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||
|
||||
// Vérifier que les nouveaux mots de passe correspondent
|
||||
if ($newPassword !== $confirmPassword) {
|
||||
$_SESSION['error_message'] = "Les nouveaux mots de passe ne correspondent pas.";
|
||||
header('Location: admin.php?action=show_change_password');
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que le nouveau mot de passe est assez long
|
||||
if (strlen($newPassword) < 8) {
|
||||
$_SESSION['error_message'] = "Le nouveau mot de passe doit faire au moins 8 caractères.";
|
||||
header('Location: admin.php?action=show_change_password');
|
||||
return;
|
||||
}
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// Vérifier l'ancien mot de passe
|
||||
$stmt = $db->prepare('SELECT password_hash FROM admins WHERE id = :id');
|
||||
$stmt->bindValue(':id', $_SESSION['admin_id'], SQLITE3_INTEGER);
|
||||
$result = $stmt->execute();
|
||||
$user = $result->fetchArray();
|
||||
|
||||
if (!password_verify($currentPassword, $user['password_hash'])) {
|
||||
$_SESSION['error_message'] = "Le mot de passe actuel est incorrect.";
|
||||
header('Location: admin.php?action=show_change_password');
|
||||
return;
|
||||
}
|
||||
|
||||
// Mettre à jour le mot de passe
|
||||
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
$stmt = $db->prepare('UPDATE admins SET password_hash = :hash WHERE id = :id');
|
||||
$stmt->bindValue(':hash', $newHash, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':id', $_SESSION['admin_id'], SQLITE3_INTEGER);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$_SESSION['success_message'] = "Mot de passe changé avec succès.";
|
||||
header('Location: admin.php');
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Une erreur est survenue lors du changement de mot de passe.";
|
||||
header('Location: admin.php?action=show_change_password');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Gérer la déconnexion
|
||||
function handleLogout() {
|
||||
session_destroy();
|
||||
header('Location: admin.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Router principal
|
||||
$action = $_GET['action'] ?? 'home';
|
||||
|
||||
switch ($action) {
|
||||
case 'login':
|
||||
handleLogin();
|
||||
break;
|
||||
|
||||
case 'logout':
|
||||
handleLogout();
|
||||
break;
|
||||
|
||||
case 'show_change_password':
|
||||
showChangePasswordForm();
|
||||
break;
|
||||
|
||||
case 'change_password':
|
||||
handlePasswordChange();
|
||||
break;
|
||||
|
||||
default:
|
||||
showAdminInterface();
|
||||
break;
|
||||
}
|
||||
?>
|
247
arbre-img.php
Normal file
247
arbre-img.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?php
|
||||
require_once 'fonctions.php';
|
||||
|
||||
// Vérifier l'authentification
|
||||
session_start();
|
||||
if (!isset($_SESSION['admin_id'])) {
|
||||
header('Location: admin.php?action=login');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Récupérer le chemin courant
|
||||
$currentPath = isset($_GET['path']) ? $_GET['path'] : './liste_albums';
|
||||
$currentPath = realpath($currentPath);
|
||||
|
||||
// Vérification de sécurité
|
||||
if (!isSecurePath($currentPath)) {
|
||||
header('Location: arbre.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Gérer l'upload des images
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['action'])) {
|
||||
switch ($_POST['action']) {
|
||||
case 'upload':
|
||||
$uploadedFiles = $_FILES['images'] ?? [];
|
||||
$successCount = 0;
|
||||
$errors = [];
|
||||
|
||||
// Gérer les uploads multiples
|
||||
for ($i = 0; $i < count($uploadedFiles['name']); $i++) {
|
||||
if ($uploadedFiles['error'][$i] === UPLOAD_ERR_OK) {
|
||||
$tmpName = $uploadedFiles['tmp_name'][$i];
|
||||
$fileName = sanitizeFilename($uploadedFiles['name'][$i]);
|
||||
$extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
||||
|
||||
// Vérifier l'extension
|
||||
if (in_array($extension, ALLOWED_EXTENSIONS)) {
|
||||
$destination = $currentPath . '/' . $fileName;
|
||||
|
||||
// Vérifier si le fichier existe déjà
|
||||
if (file_exists($destination)) {
|
||||
$baseName = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$counter = 1;
|
||||
while (file_exists($destination)) {
|
||||
$fileName = $baseName . '_' . $counter . '.' . $extension;
|
||||
$destination = $currentPath . '/' . $fileName;
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
if (move_uploaded_file($tmpName, $destination)) {
|
||||
$successCount++;
|
||||
} else {
|
||||
$errors[] = "Erreur lors du déplacement de $fileName";
|
||||
}
|
||||
} else {
|
||||
$errors[] = "Extension non autorisée pour $fileName";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($successCount > 0) {
|
||||
$_SESSION['success_message'] = "$successCount image(s) téléversée(s) avec succès.";
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
$_SESSION['error_message'] = implode("\n", $errors);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$images = $_POST['images'] ?? [];
|
||||
$deleteCount = 0;
|
||||
|
||||
foreach ($images as $image) {
|
||||
$imagePath = $currentPath . '/' . basename($image);
|
||||
if (isSecurePath($imagePath) && file_exists($imagePath)) {
|
||||
if (unlink($imagePath)) {
|
||||
$deleteCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleteCount > 0) {
|
||||
$_SESSION['success_message'] = "$deleteCount image(s) supprimée(s).";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
header('Location: arbre-img.php?path=' . urlencode($currentPath));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Récupérer les images du dossier courant
|
||||
$images = [];
|
||||
$tempImages = [];
|
||||
foreach (new DirectoryIterator($currentPath) as $file) {
|
||||
if ($file->isDot()) continue;
|
||||
if ($file->isFile()) {
|
||||
$extension = strtolower($file->getExtension());
|
||||
if (in_array($extension, ALLOWED_EXTENSIONS)) {
|
||||
$tempImages[] = [
|
||||
'name' => $file->getFilename(),
|
||||
'time' => $file->getCTime()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par date de création décroissante
|
||||
usort($tempImages, function($a, $b) {
|
||||
return $b['time'] - $a['time'];
|
||||
});
|
||||
|
||||
// Extraire uniquement les noms de fichiers
|
||||
$images = array_map(function($img) {
|
||||
return $img['name'];
|
||||
}, $tempImages);
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gestion des images - ICO</title>
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="styles-admin.css">
|
||||
</head>
|
||||
<body class="admin-page">
|
||||
<div class="admin-header">
|
||||
<h1>Gestion des images</h1>
|
||||
<div class="admin-actions">
|
||||
<button onclick="document.getElementById('imageUploadForm').click()" class="action-button action-button-success">
|
||||
Ajouter des images
|
||||
</button>
|
||||
<button onclick="deleteSelected()" id="deleteSelectedBtn" style="display: none;" class="action-button action-button-danger">
|
||||
Supprimer la sélection
|
||||
</button>
|
||||
<a href="arbre.php?path=<?php echo urlencode($currentPath); ?>" class="action-button action-button-secondary">
|
||||
Retour
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-content">
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="message success-message"><?php echo nl2br(htmlspecialchars($_SESSION['success_message'])); ?></div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="message error-message"><?php echo nl2br(htmlspecialchars($_SESSION['error_message'])); ?></div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="upload-zone" id="dropZone">
|
||||
<p>Glissez-déposez vos images ici ou cliquez sur "Ajouter des images"</p>
|
||||
<form method="post" enctype="multipart/form-data" id="uploadForm">
|
||||
<input type="hidden" name="action" value="upload">
|
||||
<input type="file" name="images[]" id="imageUploadForm" multiple accept=".jpg,.jpeg,.png,.gif">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form method="post" id="deleteForm">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<div class="images-grid">
|
||||
<?php foreach($images as $image):
|
||||
$imageUrl = getBaseUrl() . '/liste_albums/' . substr($currentPath, strpos($currentPath, '/liste_albums/') + strlen('/liste_albums/')) . '/' . $image;
|
||||
?>
|
||||
<div class="image-item">
|
||||
<input type="checkbox" name="images[]" value="<?php echo htmlspecialchars($image); ?>"
|
||||
class="image-checkbox" onchange="updateDeleteButton()">
|
||||
<img src="<?php echo htmlspecialchars($imageUrl); ?>"
|
||||
alt="<?php echo htmlspecialchars($image); ?>" loading="lazy">
|
||||
<div class="image-actions">
|
||||
<button type="button" onclick="deleteImage('<?php echo htmlspecialchars($image); ?>')"
|
||||
class="tree-button tree-button-danger">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Gestion du drag & drop
|
||||
const dropZone = document.getElementById('dropZone');
|
||||
const uploadForm = document.getElementById('uploadForm');
|
||||
const imageUploadForm = document.getElementById('imageUploadForm');
|
||||
|
||||
dropZone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('drag-over');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', () => {
|
||||
dropZone.classList.remove('drag-over');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('drag-over');
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
// Créer un objet DataTransfer
|
||||
const dataTransfer = new DataTransfer();
|
||||
// Ajouter les fichiers
|
||||
for (let file of files) {
|
||||
dataTransfer.items.add(file);
|
||||
}
|
||||
// Mettre à jour l'input
|
||||
imageUploadForm.files = dataTransfer.files;
|
||||
// Soumettre le formulaire
|
||||
uploadForm.submit();
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion de la suppression
|
||||
function deleteImage(imageName) {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer cette image ?')) {
|
||||
const form = document.getElementById('deleteForm');
|
||||
form.innerHTML = `
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="images[]" value="${imageName}">
|
||||
`;
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
function updateDeleteButton() {
|
||||
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
|
||||
const deleteBtn = document.getElementById('deleteSelectedBtn');
|
||||
deleteBtn.style.display = checkboxes.length > 0 ? 'inline-flex' : 'none';
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
|
||||
if (checkboxes.length > 0 && confirm('Êtes-vous sûr de vouloir supprimer les images sélectionnées ?')) {
|
||||
document.getElementById('deleteForm').submit();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
242
arbre.php
Normal file
242
arbre.php
Normal file
@ -0,0 +1,242 @@
|
||||
<?php
|
||||
require_once 'fonctions.php';
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION['admin_id'])) {
|
||||
header('Location: admin.php?action=login');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
$path = $_POST['path'] ?? '';
|
||||
$newName = $_POST['new_name'] ?? '';
|
||||
$description = $_POST['description'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'create_folder':
|
||||
if ($path && $newName) {
|
||||
$newPath = $path . '/' . sanitizeFilename($newName);
|
||||
if (!file_exists($newPath)) {
|
||||
mkdir($newPath, 0755, true);
|
||||
$infoContent = $newName . "\n" . $description;
|
||||
file_put_contents($newPath . '/infos.txt', $infoContent);
|
||||
$_SESSION['success_message'] = "Dossier créé avec succès.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Ce dossier existe déjà.";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'edit_folder':
|
||||
if ($path && isSecurePath($path)) {
|
||||
$infoContent = $newName . "\n" . $description;
|
||||
$infoPath = $path . '/infos.txt';
|
||||
if (file_put_contents($infoPath, $infoContent) !== false) {
|
||||
$_SESSION['success_message'] = "Dossier modifié avec succès.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Erreur lors de la modification du dossier.";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete_folder':
|
||||
if ($path && isSecurePath($path)) {
|
||||
function rrmdir($dir) {
|
||||
if (is_dir($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
if (is_dir($dir . "/" . $object)) {
|
||||
rrmdir($dir . "/" . $object);
|
||||
} else {
|
||||
unlink($dir . "/" . $object);
|
||||
}
|
||||
}
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
rrmdir($path);
|
||||
$_SESSION['success_message'] = "Dossier supprimé avec succès.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
header('Location: arbre.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$currentPath = isset($_GET['path']) ? $_GET['path'] : './liste_albums';
|
||||
$currentPath = realpath($currentPath);
|
||||
|
||||
if (!isSecurePath($currentPath)) {
|
||||
header('Location: arbre.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
function generateTree($path, $currentPath) {
|
||||
if (!is_dir($path)) return '';
|
||||
|
||||
$output = '<ul class="tree-list">';
|
||||
foreach (new DirectoryIterator($path) as $item) {
|
||||
if ($item->isDot()) continue;
|
||||
if ($item->isDir()) {
|
||||
$fullPath = $item->getPathname();
|
||||
$info = getAlbumInfo($fullPath);
|
||||
$isCurrentPath = realpath($fullPath) === $currentPath;
|
||||
$hasSubfolders = hasSubfolders($fullPath);
|
||||
|
||||
$output .= '<li class="tree-item' . ($isCurrentPath ? ' active' : '') . '">';
|
||||
$output .= '<div class="tree-item-content">';
|
||||
$output .= '<a href="?path=' . urlencode($fullPath) . '" class="tree-link">';
|
||||
$output .= '<span class="folder-icon">📁</span> ' . htmlspecialchars($info['title']);
|
||||
$output .= '</a>';
|
||||
$output .= '<div class="tree-actions">';
|
||||
if (!$hasSubfolders) {
|
||||
$output .= '<a href="arbre-img.php?path=' . urlencode($fullPath) . '" class="tree-button" style="text-decoration: none">🖼️</a>';
|
||||
}
|
||||
$output .= '<button onclick="editFolder(\'' . htmlspecialchars($fullPath) . '\', \'' . htmlspecialchars($info['title']) . '\', \'' . htmlspecialchars($info['description']) . '\')" class="tree-button">✏️</button>';
|
||||
$output .= '<button onclick="createSubfolder(\'' . htmlspecialchars($fullPath) . '\')" class="tree-button">➕</button>';
|
||||
if ($fullPath !== './liste_albums') {
|
||||
$output .= '<button onclick="deleteFolder(\'' . htmlspecialchars($fullPath) . '\')" class="tree-button tree-button-danger">🗑️</button>';
|
||||
}
|
||||
$output .= '</div></div>';
|
||||
$output .= generateTree($fullPath, $currentPath);
|
||||
$output .= '</li>';
|
||||
}
|
||||
}
|
||||
$output .= '</ul>';
|
||||
return $output;
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Arborescence - ICO</title>
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="styles-admin.css">
|
||||
</head>
|
||||
<body class="admin-page">
|
||||
<div class="admin-header">
|
||||
<h1>Gestion de l'arborescence</h1>
|
||||
<div class="admin-actions">
|
||||
<button onclick="createSubfolder('./liste_albums')" class="action-button">Nouveau dossier</button>
|
||||
<a href="admin.php" class="action-button action-button-secondary">Retour</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-content">
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="message success-message"><?php echo htmlspecialchars($_SESSION['success_message']); ?></div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_SESSION['error_message'])): ?>
|
||||
<div class="message error-message"><?php echo htmlspecialchars($_SESSION['error_message']); ?></div>
|
||||
<?php unset($_SESSION['error_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="tree-container">
|
||||
<?php echo generateTree('./liste_albums', $currentPath); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de création de dossier -->
|
||||
<div id="createFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Créer un nouveau dossier</h2>
|
||||
<form method="post" action="arbre.php">
|
||||
<input type="hidden" name="action" value="create_folder">
|
||||
<input type="hidden" name="path" id="parentPath">
|
||||
<div class="form-group">
|
||||
<label for="new_name">Nom du dossier :</label>
|
||||
<input type="text" id="new_name" name="new_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">Description :</label>
|
||||
<textarea id="description" name="description" rows="4" class="form-textarea"></textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" onclick="closeModal()" class="action-button action-button-secondary">Annuler</button>
|
||||
<button type="submit" class="action-button">Créer</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal d'édition de dossier -->
|
||||
<div id="editFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Modifier le dossier</h2>
|
||||
<form method="post" action="arbre.php">
|
||||
<input type="hidden" name="action" value="edit_folder">
|
||||
<input type="hidden" name="path" id="editPath">
|
||||
<div class="form-group">
|
||||
<label for="edit_name">Nom du dossier :</label>
|
||||
<input type="text" id="edit_name" name="new_name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edit_description">Description :</label>
|
||||
<textarea id="edit_description" name="description" rows="4" class="form-textarea"></textarea>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" onclick="closeModal()" class="action-button action-button-secondary">Annuler</button>
|
||||
<button type="submit" class="action-button">Enregistrer</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de confirmation de suppression -->
|
||||
<div id="deleteFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Confirmer la suppression</h2>
|
||||
<p>Êtes-vous sûr de vouloir supprimer ce dossier et tout son contenu ?</p>
|
||||
<form method="post" action="arbre.php">
|
||||
<input type="hidden" name="action" value="delete_folder">
|
||||
<input type="hidden" name="path" id="deletePath">
|
||||
<div class="form-actions">
|
||||
<button type="button" onclick="closeModal()" class="action-button action-button-secondary">Annuler</button>
|
||||
<button type="submit" class="action-button action-button-danger">Supprimer</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function createSubfolder(path) {
|
||||
document.getElementById('parentPath').value = path;
|
||||
document.getElementById('createFolderModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function editFolder(path, title, description) {
|
||||
document.getElementById('editPath').value = path;
|
||||
document.getElementById('edit_name').value = title;
|
||||
document.getElementById('edit_description').value = description;
|
||||
document.getElementById('editFolderModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function deleteFolder(path) {
|
||||
document.getElementById('deletePath').value = path;
|
||||
document.getElementById('deleteFolderModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('createFolderModal').style.display = 'none';
|
||||
document.getElementById('editFolderModal').style.display = 'none';
|
||||
document.getElementById('deleteFolderModal').style.display = 'none';
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.classList.contains('modal')) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
559
styles-admin.css
Normal file
559
styles-admin.css
Normal file
@ -0,0 +1,559 @@
|
||||
/* Reset et styles de base pour l'admin */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
background-color: #121212;
|
||||
color: #ffffff;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Page d'administration */
|
||||
.admin-page {
|
||||
background-color: #121212;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Page de connexion */
|
||||
.admin-login {
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
background-color: #1e1e1e;
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.admin-login h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* En-tête de l'administration */
|
||||
.admin-header {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
}
|
||||
|
||||
.admin-header h1 {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Zone de contenu principale */
|
||||
.admin-content {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
background-color: #1e1e1e;
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
min-height: calc(100vh - 12rem);
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Boutons d'action */
|
||||
.admin-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.8rem 1.5rem;
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 2rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: #1976d2;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-button-success {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.action-button-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.action-button-danger {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.action-button-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.action-button-secondary {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
||||
.action-button-secondary:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Menu administration */
|
||||
.admin-menu {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.admin-menu-item {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background: #1e1e1e;
|
||||
border: 2px solid #333;
|
||||
border-radius: 1.5rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-menu-item:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 5px 20px rgba(33, 150, 243, 0.15);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.menu-icon svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
stroke: #fff;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-menu-item:hover .menu-icon svg {
|
||||
stroke: #2196f3;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.menu-content h2 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu-content p {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -100%;
|
||||
padding: 1rem;
|
||||
background: rgba(33, 150, 243, 0.95);
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-menu-item:hover .menu-content p {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* Formulaires */
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #2a2a2a;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: 2px solid #2196f3;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #2a2a2a;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-textarea:focus {
|
||||
outline: 2px solid #2196f3;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
.message {
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Styles pour l'arborescence */
|
||||
.tree-container {
|
||||
padding: 1rem;
|
||||
background: #2a2a2a;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.tree-list {
|
||||
list-style: none;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.tree-item {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.tree-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.tree-item.active > .tree-item-content {
|
||||
background: #2196f3;
|
||||
}
|
||||
|
||||
.tree-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.folder-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.tree-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tree-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tree-button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tree-button-danger:hover {
|
||||
background: rgba(220, 53, 69, 0.2);
|
||||
}
|
||||
|
||||
.image-actions .tree-button {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.image-actions .tree-button:hover {
|
||||
background-color: rgba(220, 53, 69, 0.9);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tree-button-danger {
|
||||
background-color: rgba(220, 53, 69, 0.7);
|
||||
}
|
||||
|
||||
/* Styles pour le toggle des sous-dossiers */
|
||||
.tree-item-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background: #1e1e1e;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0.25rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.toggle-spacer {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.subtree {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.subtree.active {
|
||||
max-height: 1000px;
|
||||
opacity: 1;
|
||||
transition: max-height 0.3s ease-in, opacity 0.3s ease-in;
|
||||
}
|
||||
|
||||
/* Ajuster les marges et le padding pour une meilleure hiérarchie visuelle */
|
||||
.tree-list {
|
||||
list-style: none;
|
||||
padding-left: 1.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tree-list:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* Animation pour le hover des boutons */
|
||||
.toggle-btn:hover {
|
||||
color: #2196f3;
|
||||
background: rgba(33, 150, 243, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Styles pour les modales */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #1e1e1e;
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.modal h2 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Styles pour les listes d'images */
|
||||
.images-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background-color: #2a2a2a;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.image-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.image-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.image-item:hover img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.image-checkbox {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
z-index: 2;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
accent-color: #2196f3;
|
||||
}
|
||||
|
||||
.image-checkbox:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.upload-zone {
|
||||
border: 2px dashed #2196f3;
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
background-color: rgba(33, 150, 243, 0.1);
|
||||
}
|
||||
|
||||
.upload-zone.drag-over {
|
||||
background-color: rgba(33, 150, 243, 0.2);
|
||||
border-color: #1976d2;
|
||||
}
|
||||
|
||||
#imageUploadForm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Media Queries */
|
||||
@media (max-width: 768px) {
|
||||
.admin-page {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.admin-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.admin-menu {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accessibilité */
|
||||
a:focus-visible,
|
||||
button:focus-visible,
|
||||
input:focus-visible {
|
||||
outline: 2px solid #2196f3;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user