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:
2026-01-12 03:29:01 +01:00
commit bd6d321ed7
24 changed files with 6463 additions and 0 deletions

428
assets/css/file-tree.css Normal file
View File

@@ -0,0 +1,428 @@
/**
* FuZip - Styles arborescence de fichiers
* Tree view, checkboxes, sélection exclusive, animations
*/
/* ===== Tree Panel ===== */
.tree-panel {
background-color: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 400px;
}
.tree-header {
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid var(--color-border);
background-color: var(--color-bg-secondary);
display: flex;
gap: var(--spacing-md);
align-items: center;
}
.search-input-wrapper {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
.search-input {
flex: 1;
width: 100%;
padding: var(--spacing-sm) 2.5rem var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
font-size: var(--font-size-sm);
background-color: var(--color-bg);
color: var(--color-text);
transition: all var(--transition-fast);
}
.search-input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-primary-light);
}
.search-input::placeholder {
color: var(--color-text-muted);
}
.search-clear-btn {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
padding: 0.25rem;
background: transparent;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
transition: all var(--transition-fast);
}
.search-clear-btn:hover {
background-color: rgba(0, 0, 0, 0.1);
}
/* Mode dark : hover plus visible */
:root[data-theme="dark"] .search-clear-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.search-clear-btn svg {
width: 1rem;
height: 1rem;
stroke: var(--color-text-muted);
fill: none;
stroke-width: 2;
}
.search-clear-btn.hidden {
display: none;
}
.tree-actions {
display: flex;
gap: var(--spacing-xs);
}
.btn-tree-action {
padding: var(--spacing-sm);
background-color: transparent;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all var(--transition-fast);
}
.btn-tree-action:hover {
background-color: var(--color-bg-tertiary);
border-color: var(--color-border-hover);
}
.btn-tree-action svg {
width: 1.25rem;
height: 1.25rem;
color: var(--color-text-secondary);
}
/* ===== Tree Content ===== */
.tree-content {
flex: 1;
overflow-y: auto;
padding: var(--spacing-sm);
}
/* Scrollbar personnalisé */
.tree-content::-webkit-scrollbar {
width: 8px;
}
.tree-content::-webkit-scrollbar-track {
background: var(--color-bg-secondary);
}
.tree-content::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: var(--radius-md);
}
.tree-content::-webkit-scrollbar-thumb:hover {
background: var(--color-border-hover);
}
/* ===== Tree Items ===== */
.tree-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
cursor: pointer;
transition: background-color var(--transition-fast);
position: relative;
user-select: none;
}
.tree-item:hover {
background-color: var(--color-bg-secondary);
}
.tree-item.selected {
background-color: var(--color-primary-light);
}
/* Indentation pour niveaux */
.tree-item[data-level="1"] { padding-left: calc(var(--spacing-sm) + 0rem); }
.tree-item[data-level="2"] { padding-left: calc(var(--spacing-sm) + 1.5rem); }
.tree-item[data-level="3"] { padding-left: calc(var(--spacing-sm) + 3rem); }
.tree-item[data-level="4"] { padding-left: calc(var(--spacing-sm) + 4.5rem); }
.tree-item[data-level="5"] { padding-left: calc(var(--spacing-sm) + 6rem); }
/* Icône expand/collapse pour dossiers */
.tree-expand {
width: 1rem;
height: 1rem;
flex-shrink: 0;
transition: transform var(--transition-fast);
cursor: pointer;
fill: none;
stroke: var(--color-text-muted);
stroke-width: 2;
}
.tree-item.expanded .tree-expand {
transform: rotate(90deg);
}
.tree-item.collapsed .tree-expand {
transform: rotate(0deg);
}
/* Pas d'icône pour fichiers */
.tree-item.file .tree-expand {
visibility: hidden;
}
/* Checkbox */
.tree-checkbox {
width: 1.125rem;
height: 1.125rem;
border: 2px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
flex-shrink: 0;
transition: all var(--transition-fast);
position: relative;
background-color: var(--color-bg);
}
.tree-checkbox:hover {
border-color: var(--color-primary);
}
/* Checkbox cochée */
input[type="checkbox"]:checked + .tree-checkbox,
.tree-checkbox.checked {
background-color: var(--color-primary);
border-color: var(--color-primary);
}
/* Icône checkmark */
input[type="checkbox"]:checked + .tree-checkbox::after,
.tree-checkbox.checked::after {
content: '';
position: absolute;
left: 0.25rem;
top: 0.125rem;
width: 0.375rem;
height: 0.625rem;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
/* État indeterminate pour dossiers partiellement sélectionnés */
.tree-checkbox.indeterminate {
background-color: var(--color-primary-light);
border-color: var(--color-primary);
}
.tree-checkbox.indeterminate::after {
content: '';
position: absolute;
left: 0.1875rem;
top: 50%;
width: 0.5rem;
height: 2px;
background-color: var(--color-primary);
transform: translateY(-50%);
}
/* Masquer input natif */
.tree-item input[type="checkbox"] {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* Icône fichier/dossier */
.tree-icon {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
fill: none;
stroke-width: 2;
}
.tree-icon.folder {
stroke: #f59e0b;
}
.tree-icon.file {
stroke: var(--color-text-muted);
}
/* Nom du fichier/dossier */
.tree-name {
flex: 1;
font-size: var(--font-size-sm);
color: var(--color-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Taille fichier */
.tree-size {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
flex-shrink: 0;
}
/* ===== États spéciaux ===== */
/* Fichier en conflit */
.tree-item.conflict {
position: relative;
}
.tree-item.conflict::before {
content: '⚠';
position: absolute;
left: 0.25rem;
font-size: 0.875rem;
color: var(--color-warning);
}
.tree-item.conflict .tree-name {
font-weight: 500;
}
/* Animation déselection automatique (sélection exclusive) */
@keyframes deselect-pulse {
0%, 100% {
background-color: transparent;
transform: scale(1);
}
25% {
background-color: rgba(255, 193, 7, 0.4);
transform: scale(1.02);
}
75% {
background-color: rgba(255, 193, 7, 0.2);
transform: scale(1.01);
}
}
.tree-item.auto-deselected {
animation: deselect-pulse 0.6s ease-in-out;
}
/* Highlight pour recherche */
.tree-item.search-match {
background-color: #fef3c7;
}
.tree-item.search-match .tree-name {
font-weight: 600;
}
/* Élément caché (filtré par recherche) */
.tree-item.filtered-out {
display: none;
}
/* Dossier vide */
.tree-item.empty {
opacity: 0.6;
font-style: italic;
}
/* Actions sur l'item (preview, extract) */
.tree-actions {
display: none;
gap: var(--spacing-xs);
margin-left: auto;
}
.tree-item:hover .tree-actions {
display: flex;
}
.btn-item-action {
padding: var(--spacing-xs);
background-color: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all var(--transition-fast);
}
.btn-item-action:hover {
background-color: var(--color-bg-tertiary);
border-color: var(--color-border);
}
.btn-item-action svg {
width: 1rem;
height: 1rem;
stroke: var(--color-text-secondary);
fill: none;
stroke-width: 2;
}
/* ===== Empty State ===== */
.tree-empty {
padding: var(--spacing-2xl);
text-align: center;
color: var(--color-text-muted);
}
.tree-empty-icon {
width: 3rem;
height: 3rem;
margin: 0 auto var(--spacing-md);
color: var(--color-text-muted);
}
.tree-empty-text {
font-size: var(--font-size-base);
}
/* ===== Responsive ===== */
@media (max-width: 768px) {
.tree-panel {
min-height: 300px;
}
.tree-header {
flex-direction: column;
align-items: stretch;
}
.tree-actions {
justify-content: flex-end;
}
/* Réduire l'indentation sur mobile */
.tree-item[data-level="2"] { padding-left: calc(var(--spacing-sm) + 1rem); }
.tree-item[data-level="3"] { padding-left: calc(var(--spacing-sm) + 2rem); }
.tree-item[data-level="4"] { padding-left: calc(--spacing-sm) + 3rem); }
.tree-item[data-level="5"] { padding-left: calc(var(--spacing-sm) + 4rem); }
}