Initial commit: FuZip - Application de fusion interactive de fichiers ZIP
- Backend PHP: architecture MVC avec API REST (upload, merge, preview, extract) - Frontend JavaScript: composants modulaires (arborescence, upload, themes, i18n) - Fonctionnalités: drag&drop, sélection exclusive, détection conflits, persistance état - Sécurité: validation stricte, isolation sessions, sanitization chemins - UI/UX: responsive, thèmes clair/sombre, multi-langue (FR/EN) - Documentation: README complet avec installation et utilisation
This commit is contained in:
368
index.php
Normal file
368
index.php
Normal file
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
/**
|
||||
* FuZip - Interface principale
|
||||
* Application de fusion interactive de fichiers ZIP
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/core/Config.php';
|
||||
require_once __DIR__ . '/core/SessionManager.php';
|
||||
|
||||
// Initialiser la session
|
||||
try {
|
||||
$sessionId = SessionManager::init();
|
||||
Config::log("Page principale chargée - Session : {$sessionId}");
|
||||
} catch (Exception $e) {
|
||||
die("Erreur initialisation : " . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
|
||||
// Langue par défaut (peut être changée par l'utilisateur)
|
||||
$lang = $_GET['lang'] ?? 'fr';
|
||||
if (!in_array($lang, Config::SUPPORTED_LANGUAGES)) {
|
||||
$lang = Config::DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
// Textes multilingues
|
||||
$i18n = [
|
||||
'fr' => [
|
||||
'title' => 'FuZip - Fusion de Fichiers ZIP',
|
||||
'subtitle' => 'Fusionnez deux fichiers ZIP en choisissant les fichiers à conserver',
|
||||
'upload_left' => 'ZIP Gauche',
|
||||
'upload_right' => 'ZIP Droite',
|
||||
'drag_drop' => 'Glissez un fichier ZIP ici',
|
||||
'or' => 'ou',
|
||||
'browse' => 'Parcourir',
|
||||
'no_file' => 'Aucun fichier',
|
||||
'files_count' => 'fichiers',
|
||||
'total_size' => 'Taille totale',
|
||||
'search_placeholder' => 'Rechercher un fichier...',
|
||||
'select_all' => 'Tout sélectionner',
|
||||
'deselect_all' => 'Tout désélectionner',
|
||||
'expand_all' => 'Tout déplier',
|
||||
'collapse_all' => 'Tout replier',
|
||||
'conflicts' => 'Conflits détectés',
|
||||
'merge_button' => 'Fusionner et Télécharger',
|
||||
'reset_button' => 'Réinitialiser',
|
||||
'selected_files' => 'fichiers sélectionnés',
|
||||
'loading' => 'Chargement...',
|
||||
'theme_toggle' => 'Changer de thème',
|
||||
'lang_toggle' => 'Language'
|
||||
],
|
||||
'en' => [
|
||||
'title' => 'FuZip - ZIP Files Merger',
|
||||
'subtitle' => 'Merge two ZIP files by choosing which files to keep',
|
||||
'upload_left' => 'Left ZIP',
|
||||
'upload_right' => 'Right ZIP',
|
||||
'drag_drop' => 'Drop a ZIP file here',
|
||||
'or' => 'or',
|
||||
'browse' => 'Browse',
|
||||
'no_file' => 'No file',
|
||||
'files_count' => 'files',
|
||||
'total_size' => 'Total size',
|
||||
'search_placeholder' => 'Search for a file...',
|
||||
'select_all' => 'Select all',
|
||||
'deselect_all' => 'Deselect all',
|
||||
'expand_all' => 'Expand all',
|
||||
'collapse_all' => 'Collapse all',
|
||||
'conflicts' => 'Conflicts detected',
|
||||
'merge_button' => 'Merge and Download',
|
||||
'reset_button' => 'Reset',
|
||||
'selected_files' => 'files selected',
|
||||
'loading' => 'Loading...',
|
||||
'theme_toggle' => 'Toggle theme',
|
||||
'lang_toggle' => 'Langue'
|
||||
]
|
||||
];
|
||||
|
||||
$t = $i18n[$lang];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= $lang ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($t['title']) ?></title>
|
||||
|
||||
<!-- Styles CSS -->
|
||||
<link rel="stylesheet" href="assets/css/main.css">
|
||||
<link rel="stylesheet" href="assets/css/upload-panel.css">
|
||||
<link rel="stylesheet" href="assets/css/file-tree.css">
|
||||
<link rel="stylesheet" href="assets/css/themes.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<svg class="logo-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
<h1 class="logo-text">FuZip</h1>
|
||||
</div>
|
||||
|
||||
<p class="subtitle"><?= htmlspecialchars($t['subtitle']) ?></p>
|
||||
|
||||
<div class="header-actions">
|
||||
<!-- Toggle langue -->
|
||||
<button class="btn-icon" id="btn-lang-toggle" title="<?= htmlspecialchars($t['lang_toggle']) ?>">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="2" y1="12" x2="22" y2="12"></line>
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
|
||||
</svg>
|
||||
<span class="lang-text"><?= strtoupper($lang) ?></span>
|
||||
</button>
|
||||
|
||||
<!-- Toggle thème -->
|
||||
<button class="btn-icon" id="btn-theme-toggle" title="<?= htmlspecialchars($t['theme_toggle']) ?>">
|
||||
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Conteneur principal -->
|
||||
<main class="main-container">
|
||||
<!-- Zones d'upload -->
|
||||
<section class="upload-section">
|
||||
<!-- Upload gauche -->
|
||||
<div class="upload-panel" id="upload-panel-left" data-side="left">
|
||||
<h2 class="panel-title"><?= htmlspecialchars($t['upload_left']) ?></h2>
|
||||
|
||||
<div class="drop-zone" id="drop-zone-left">
|
||||
<svg class="drop-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
<p class="drop-text"><?= htmlspecialchars($t['drag_drop']) ?></p>
|
||||
<p class="drop-or"><?= htmlspecialchars($t['or']) ?></p>
|
||||
<button class="btn-browse" id="btn-browse-left"><?= htmlspecialchars($t['browse']) ?></button>
|
||||
<input type="file" class="file-input" id="file-input-left" accept=".zip,application/zip" hidden>
|
||||
</div>
|
||||
|
||||
<div class="upload-info hidden" id="upload-info-left">
|
||||
<div class="file-name" id="file-name-left"><?= htmlspecialchars($t['no_file']) ?></div>
|
||||
<div class="file-stats">
|
||||
<span class="stat-item">
|
||||
<svg class="stat-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
|
||||
<polyline points="13 2 13 9 20 9"></polyline>
|
||||
</svg>
|
||||
<span class="stat-value" id="file-count-left">0</span> <?= htmlspecialchars($t['files_count']) ?>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<svg class="stat-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
</svg>
|
||||
<span class="stat-value" id="file-size-left">0 B</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="upload-progress hidden" id="progress-bar-left">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progress-fill-left" style="width: 0%"></div>
|
||||
</div>
|
||||
<span class="progress-text" id="progress-text-left">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload droite -->
|
||||
<div class="upload-panel" id="upload-panel-right" data-side="right">
|
||||
<h2 class="panel-title"><?= htmlspecialchars($t['upload_right']) ?></h2>
|
||||
|
||||
<div class="drop-zone" id="drop-zone-right">
|
||||
<svg class="drop-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
<p class="drop-text"><?= htmlspecialchars($t['drag_drop']) ?></p>
|
||||
<p class="drop-or"><?= htmlspecialchars($t['or']) ?></p>
|
||||
<button class="btn-browse" id="btn-browse-right"><?= htmlspecialchars($t['browse']) ?></button>
|
||||
<input type="file" class="file-input" id="file-input-right" accept=".zip,application/zip" hidden>
|
||||
</div>
|
||||
|
||||
<div class="upload-info hidden" id="upload-info-right">
|
||||
<div class="file-name" id="file-name-right"><?= htmlspecialchars($t['no_file']) ?></div>
|
||||
<div class="file-stats">
|
||||
<span class="stat-item">
|
||||
<svg class="stat-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
|
||||
<polyline points="13 2 13 9 20 9"></polyline>
|
||||
</svg>
|
||||
<span class="stat-value" id="file-count-right">0</span> <?= htmlspecialchars($t['files_count']) ?>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<svg class="stat-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
</svg>
|
||||
<span class="stat-value" id="file-size-right">0 B</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="upload-progress hidden" id="progress-bar-right">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progress-fill-right" style="width: 0%"></div>
|
||||
</div>
|
||||
<span class="progress-text" id="progress-text-right">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Bandeau de conflits -->
|
||||
<div class="conflicts-banner hidden" id="conflicts-banner">
|
||||
<svg class="banner-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
|
||||
<line x1="12" y1="9" x2="12" y2="13"></line>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</svg>
|
||||
<span class="banner-text">
|
||||
<strong><span id="conflicts-count">0</span> <?= htmlspecialchars($t['conflicts']) ?></strong>
|
||||
- Sélectionnez un côté pour chaque fichier en conflit
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Section arborescences -->
|
||||
<section class="trees-section" id="trees-section">
|
||||
<!-- Arbre gauche -->
|
||||
<div class="tree-panel" data-side="left">
|
||||
<div class="tree-header">
|
||||
<input type="text" class="search-input" placeholder="<?= htmlspecialchars($t['search_placeholder']) ?>">
|
||||
<div class="tree-actions">
|
||||
<button class="btn-tree-action" title="<?= htmlspecialchars($t['select_all']) ?>">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 11 12 14 22 4"></polyline>
|
||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-tree-action" title="<?= htmlspecialchars($t['expand_all']) ?>">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-content" id="tree-left">
|
||||
<!-- Arborescence générée par JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arbre droite -->
|
||||
<div class="tree-panel" data-side="right">
|
||||
<div class="tree-header">
|
||||
<input type="text" class="search-input" placeholder="<?= htmlspecialchars($t['search_placeholder']) ?>">
|
||||
<div class="tree-actions">
|
||||
<button class="btn-tree-action" title="<?= htmlspecialchars($t['select_all']) ?>">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 11 12 14 22 4"></polyline>
|
||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-tree-action" title="<?= htmlspecialchars($t['expand_all']) ?>">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-content" id="tree-right">
|
||||
<!-- Arborescence générée par JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer avec boutons d'action -->
|
||||
<footer class="footer" id="footer">
|
||||
<div class="footer-content">
|
||||
<div class="selection-info">
|
||||
<svg class="info-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="9 11 12 14 22 4"></polyline>
|
||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
||||
</svg>
|
||||
<span id="selection-count">0</span> <?= htmlspecialchars($t['selected_files']) ?>
|
||||
</div>
|
||||
|
||||
<div class="footer-actions">
|
||||
<button class="btn btn-secondary" id="btn-reset">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="1 4 1 10 7 10"></polyline>
|
||||
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
|
||||
</svg>
|
||||
<?= htmlspecialchars($t['reset_button']) ?>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary" id="btn-merge" disabled>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
<?= htmlspecialchars($t['merge_button']) ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<!-- Loading overlay -->
|
||||
<div class="loading-overlay hidden" id="loading-overlay">
|
||||
<div class="loading-spinner"></div>
|
||||
<p class="loading-text" id="loading-text"><?= htmlspecialchars($t['loading']) ?></p>
|
||||
</div>
|
||||
|
||||
<!-- Modal de prévisualisation -->
|
||||
<div class="modal hidden" id="preview-modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="preview-title">Prévisualisation</h3>
|
||||
<button class="modal-close" id="preview-close">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre class="preview-content" id="preview-content"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts JavaScript -->
|
||||
<script>
|
||||
// Configuration globale
|
||||
window.FUZIP_CONFIG = {
|
||||
lang: '<?= $lang ?>',
|
||||
sessionId: '<?= $sessionId ?>',
|
||||
apiBase: 'api/',
|
||||
maxFileSize: <?= Config::MAX_FILE_SIZE ?>,
|
||||
i18n: <?= json_encode($i18n[$lang]) ?>
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Managers -->
|
||||
<script src="assets/js/ThemeManager.js" defer></script>
|
||||
<script src="assets/js/LanguageManager.js" defer></script>
|
||||
<script src="assets/js/UploadManager.js" defer></script>
|
||||
<script src="assets/js/FileTreeRenderer.js" defer></script>
|
||||
<script src="assets/js/PreviewManager.js" defer></script>
|
||||
|
||||
<!-- App principale -->
|
||||
<script src="assets/js/app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user