data-primals-engine 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/README.md +8 -0
- package/client/index.html +31 -0
- package/client/index.js +25 -0
- package/client/package-lock.json +11664 -0
- package/client/package.json +70 -0
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
- package/client/public/doc/API.postman_collection.json +214 -0
- package/client/public/github.svg +1 -0
- package/client/public/profilCompany.jpg +0 -0
- package/client/public/profilDeveloper.jpg +0 -0
- package/client/public/profilEngineer.jpg +0 -0
- package/client/public/profilPersonal.jpg +0 -0
- package/client/public/profils .psd +0 -0
- package/client/public/react.svg +9 -0
- package/client/src/APIInfo.jsx +328 -0
- package/client/src/AddWidgetTypeModal.jsx +44 -0
- package/client/src/AddWidgetTypeModal.scss +87 -0
- package/client/src/App.css +42 -0
- package/client/src/App.jsx +586 -0
- package/client/src/App.scss +1466 -0
- package/client/src/AssistantChat.jsx +280 -0
- package/client/src/AssistantChat.scss +229 -0
- package/client/src/Button.jsx +12 -0
- package/client/src/Button.scss +197 -0
- package/client/src/CalculationBuilder.jsx +338 -0
- package/client/src/CalculationStepRow.jsx +199 -0
- package/client/src/CalendarConfigModal.jsx +49 -0
- package/client/src/ChartConfigModal.jsx +402 -0
- package/client/src/ConditionBuilder.jsx +622 -0
- package/client/src/ConditionBuilder.scss +672 -0
- package/client/src/ConditionBuilder2.jsx +757 -0
- package/client/src/ContentView.jsx +274 -0
- package/client/src/CronBuilder.jsx +126 -0
- package/client/src/CronBuilder.scss +128 -0
- package/client/src/CronPartBuilder.jsx +150 -0
- package/client/src/Dashboard.jsx +210 -0
- package/client/src/Dashboard.scss +445 -0
- package/client/src/DashboardChart.jsx +362 -0
- package/client/src/DashboardFlexViewItem.jsx +129 -0
- package/client/src/DashboardView.jsx +554 -0
- package/client/src/DataChart.jsx +349 -0
- package/client/src/DataEditor.jsx +556 -0
- package/client/src/DataLayout.jsx +709 -0
- package/client/src/DataTable.jsx +996 -0
- package/client/src/DataTable.scss +73 -0
- package/client/src/Dialog.jsx +90 -0
- package/client/src/Dialog.scss +117 -0
- package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
- package/client/src/DocumentationPageLayout.jsx +93 -0
- package/client/src/DocumentationPageLayout.scss +262 -0
- package/client/src/Draggable.jsx +222 -0
- package/client/src/Draggable.scss +49 -0
- package/client/src/ExportDialog.jsx +172 -0
- package/client/src/Field.jsx +1558 -0
- package/client/src/FlexBuilder.jsx +269 -0
- package/client/src/FlexBuilder.scss +226 -0
- package/client/src/FlexBuilderControls.jsx +170 -0
- package/client/src/FlexBuilderModal.jsx +75 -0
- package/client/src/FlexBuilderModal.scss +109 -0
- package/client/src/FlexBuilderPreview.jsx +48 -0
- package/client/src/FlexBuilderUtils.js +84 -0
- package/client/src/FlexDataRenderer.jsx +170 -0
- package/client/src/FlexDataRenderer.scss +80 -0
- package/client/src/FlexNode.jsx +290 -0
- package/client/src/FlexTreeUtils.js +129 -0
- package/client/src/HiddenableCell.jsx +46 -0
- package/client/src/HiddenableCell.scss +36 -0
- package/client/src/KPIDialog.jsx +39 -0
- package/client/src/KPIWidget.jsx +139 -0
- package/client/src/KanbanCard.jsx +92 -0
- package/client/src/KanbanColumn.jsx +29 -0
- package/client/src/KanbanConfigModal.jsx +115 -0
- package/client/src/KanbanView.jsx +113 -0
- package/client/src/KanbanView.scss +107 -0
- package/client/src/MessageRotator.jsx +77 -0
- package/client/src/MessageRotator.scss +14 -0
- package/client/src/ModelCreator.jsx +640 -0
- package/client/src/ModelCreator.scss +154 -0
- package/client/src/ModelCreatorField.jsx +810 -0
- package/client/src/ModelImporter.jsx +120 -0
- package/client/src/ModelList.jsx +205 -0
- package/client/src/Notification.jsx +137 -0
- package/client/src/Notification.scss +126 -0
- package/client/src/NotificationProvider.jsx +73 -0
- package/client/src/PackGallery.jsx +211 -0
- package/client/src/PackGallery.scss +156 -0
- package/client/src/Pagination.jsx +141 -0
- package/client/src/RTE.jsx +415 -0
- package/client/src/RTETrans.jsx +103 -0
- package/client/src/RelationField.jsx +247 -0
- package/client/src/RelationValue.jsx +230 -0
- package/client/src/RestoreConfirmationModal.jsx +183 -0
- package/client/src/RestoreDialog.jsx +134 -0
- package/client/src/RestoreDialog.scss +67 -0
- package/client/src/RichTextEditorModal.jsx +34 -0
- package/client/src/RichTextEditorModal.scss +54 -0
- package/client/src/TourSpotlight.jsx +234 -0
- package/client/src/TourSpotlight.scss +100 -0
- package/client/src/TutorialsMenu.jsx +83 -0
- package/client/src/TutorialsMenu.scss +217 -0
- package/client/src/TutorialsRewardModal.jsx +65 -0
- package/client/src/Utils.jsx +78 -0
- package/client/src/ViewSwitcher.jsx +54 -0
- package/client/src/ViewSwitcher.scss +74 -0
- package/client/src/Webpage.jsx +69 -0
- package/client/src/_variables.scss +21 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/constants.js +68 -0
- package/client/src/contexts/AuthContext.jsx +28 -0
- package/client/src/contexts/ModelContext.jsx +328 -0
- package/client/src/contexts/UIContext.jsx +52 -0
- package/client/src/core/data.js +35 -0
- package/client/src/entry-client.jsx +15 -0
- package/client/src/entry-server.jsx +13 -0
- package/client/src/filter.js +222 -0
- package/client/src/hooks/data.js +101 -0
- package/client/src/hooks/useDebounce.js +20 -0
- package/client/src/hooks/useDragAndDrop.js +59 -0
- package/client/src/hooks/useLocalStorage.js +100 -0
- package/client/src/hooks/useMount.js +42 -0
- package/client/src/hooks/useTutorials.jsx +227 -0
- package/client/src/hooks/useWindowSize.js +24 -0
- package/client/src/i18n.js +47 -0
- package/client/src/index.css +90 -0
- package/client/src/index.js +10 -0
- package/client/src/main.jsx +11 -0
- package/client/src/translations.js +15940 -0
- package/client/src/tutorials.js +112 -0
- package/client/vite.config.js +30 -0
- package/eslint.config.js +50 -0
- package/package.json +48 -27
- package/server.js +9 -0
- package/src/constants.js +12 -23
- package/src/core.js +9 -8
- package/src/data.js +81 -80
- package/src/defaultModels.js +114 -115
- package/src/email.js +8 -9
- package/src/engine.js +5 -5
- package/src/events.js +1 -2
- package/src/i18n.js +5390 -211
- package/src/middlewares/middleware-mongodb.js +94 -93
- package/src/migrate.js +24 -24
- package/src/modules/assistant.js +55 -55
- package/src/modules/bucket.js +10 -9
- package/src/modules/data.js +387 -357
- package/src/modules/file.js +3 -6
- package/src/modules/mongodb.js +6 -7
- package/src/modules/user.js +24 -47
- package/src/modules/workflow.js +68 -67
- package/src/openai.jobs.js +2 -2
- package/src/packs.js +30 -31
- package/src/providers.js +101 -3
- package/src/setenv.js +1 -1
- package/src/user.js +0 -17
- package/src/workers/crypto-worker.js +21 -20
- package/src/workers/import-export-worker.js +18 -18
- package/test/data.backup.integration.test.js +7 -12
- package/test/data.integration.test.js +27 -25
- package/test/globalSetup.js +2 -2
- package/test/import_export.integration.test.js +6 -6
- package/test/model.integration.test.js +21 -22
- package/test/workflow.integration.test.js +2 -5
- package/test/workflow.robustness.test.js +6 -13
- package/vitest.config.js +2 -2
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// C:/Dev/hackersonline-engine/client/src/DocumentationPageLayout.scss
|
|
2
|
+
@import "_variables";
|
|
3
|
+
|
|
4
|
+
.documentation-layout {
|
|
5
|
+
display: flex;
|
|
6
|
+
// Ajustez 60px en fonction de la hauteur réelle de votre topBarMenu si elle est fixe
|
|
7
|
+
// Sinon, vous pourriez avoir besoin de JavaScript pour calculer la hauteur disponible
|
|
8
|
+
// ou utiliser des techniques CSS plus avancées comme Grid/Flexbox pour le layout global de la page.
|
|
9
|
+
// Pour l'instant, on part sur une hauteur qui remplit le reste de la vue.
|
|
10
|
+
height: calc(100vh - 70px); // Exemple de hauteur, ajustez selon votre TopBar
|
|
11
|
+
margin-top: 1rem; // Un peu d'espace sous la TopBar si elle est fixe et que ce layout est en dessous
|
|
12
|
+
|
|
13
|
+
.documentation-sidebar {
|
|
14
|
+
width: 100%;
|
|
15
|
+
flex:0.4;
|
|
16
|
+
min-width: 220px; // Largeur minimale
|
|
17
|
+
background-color: #f8f9fa; // Couleur de fond claire pour le menu
|
|
18
|
+
padding: 20px;
|
|
19
|
+
border-right: 1px solid #dee2e6; // Bordure de séparation
|
|
20
|
+
overflow-y: auto; // Permet le défilement si le contenu du menu est trop long
|
|
21
|
+
height: 100%; // Prend toute la hauteur disponible dans .documentation-layout
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
.documentation-menu {
|
|
24
|
+
// Styles pour le conteneur nav de DocumentationMenu.jsx
|
|
25
|
+
.doc-menu-group {
|
|
26
|
+
margin-bottom: 1.25rem; // Espace entre les groupes de menu
|
|
27
|
+
|
|
28
|
+
.doc-menu-group-title {
|
|
29
|
+
font-weight: bold;
|
|
30
|
+
font-size: 1.1em;
|
|
31
|
+
color: #343a40; // Couleur de texte foncée pour les titres de groupe
|
|
32
|
+
margin-bottom: 0.75rem;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
background: none;
|
|
35
|
+
border: none;
|
|
36
|
+
padding: 8px;
|
|
37
|
+
width: 100%;
|
|
38
|
+
text-align: left;
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
transition: color 0.2s ease-in-out;
|
|
42
|
+
|
|
43
|
+
svg {
|
|
44
|
+
margin-right: 10px; // Espace entre l'icône et le texte
|
|
45
|
+
font-size: 0.8em; // Taille de l'icône chevron
|
|
46
|
+
transition: transform 0.2s ease-in-out; // Animation douce pour la rotation
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&:hover {
|
|
50
|
+
color: $primary-color; // Utilise votre variable de couleur primaire ou un bleu par défaut
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.doc-menu-sublist {
|
|
55
|
+
list-style: none;
|
|
56
|
+
padding-left: 8px; // Indentation pour les sous-éléments, alignée avec l'icône
|
|
57
|
+
margin-top: 0.5rem;
|
|
58
|
+
border-left: 2px solid #e0e0e0; // Ligne verticale discrète pour l'indentation
|
|
59
|
+
margin-left: 4px; // Pour aligner la bordure avec le centre de l'icône
|
|
60
|
+
|
|
61
|
+
li {
|
|
62
|
+
margin-bottom: 2px; // Petit espace entre les liens
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.doc-menu-item {
|
|
66
|
+
display: block;
|
|
67
|
+
padding: 10px 15px; // Padding confortable
|
|
68
|
+
text-decoration: none;
|
|
69
|
+
color: #495057; // Couleur de texte pour les liens
|
|
70
|
+
border-radius: 5px; // Coins arrondis
|
|
71
|
+
font-size: 0.95em;
|
|
72
|
+
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
|
73
|
+
|
|
74
|
+
&:hover {
|
|
75
|
+
background-color: #e9ecef; // Fond au survol
|
|
76
|
+
color: $primary-color-darker; // Couleur de texte plus foncée au survol
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&.active {
|
|
80
|
+
background-color: $primary-color; // Couleur primaire pour l'élément actif
|
|
81
|
+
color: white; // Texte blanc pour l'élément actif
|
|
82
|
+
font-weight: 500; // Un peu plus gras pour l'élément actif
|
|
83
|
+
|
|
84
|
+
&:hover {
|
|
85
|
+
background-color: $primary-color-darker; // Couleur primaire plus foncée au survol de l'actif
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
position: relative;
|
|
91
|
+
a {
|
|
92
|
+
color: inherit;
|
|
93
|
+
display: block;
|
|
94
|
+
position: absolute;
|
|
95
|
+
top: 0;
|
|
96
|
+
left: 0;
|
|
97
|
+
width: 100%;
|
|
98
|
+
height: 100%;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.documentation-content {
|
|
107
|
+
width: 100%;
|
|
108
|
+
box-sizing: border-box;
|
|
109
|
+
flex: 1; // Prend le reste de la largeur
|
|
110
|
+
padding: 25px 40px; // Bon padding intérieur
|
|
111
|
+
overflow-y: auto; // Permet le défilement du contenu principal
|
|
112
|
+
background-color: #ffffff; // Fond blanc pour le contenu
|
|
113
|
+
height: 100%; // Prend toute la hauteur disponible
|
|
114
|
+
|
|
115
|
+
// Styles de base pour le contenu de la documentation
|
|
116
|
+
h1, h2, h3, h4 {
|
|
117
|
+
color: #212529; // Couleur de texte foncée pour les titres
|
|
118
|
+
margin-top: 1.5em; // Espace au-dessus des titres
|
|
119
|
+
margin-bottom: 0.8em;
|
|
120
|
+
padding-bottom: 0.4em;
|
|
121
|
+
border-bottom: 1px solid #eaecef; // Ligne de séparation sous les titres
|
|
122
|
+
|
|
123
|
+
&:first-child {
|
|
124
|
+
margin-top: 0; // Pas de marge en haut pour le premier titre de la page
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
h1 { font-size: 2.2em; font-weight: 600; }
|
|
128
|
+
h2 { font-size: 1.8em; font-weight: 600; }
|
|
129
|
+
h3 { font-size: 1.5em; font-weight: 600; }
|
|
130
|
+
h4 { font-size: 1.25em; font-weight: 600; }
|
|
131
|
+
|
|
132
|
+
p, li {
|
|
133
|
+
line-height: 1.75; // Bonne lisibilité pour les paragraphes et listes
|
|
134
|
+
margin-bottom: 1.1em;
|
|
135
|
+
color: #333; // Couleur de texte principale
|
|
136
|
+
font-size: 1em; // Taille de police standard
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
li > p {
|
|
140
|
+
display: inline;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
ul, ol {
|
|
144
|
+
padding-left: 30px; // Indentation pour les listes
|
|
145
|
+
ul, ol {
|
|
146
|
+
margin-top: 0.5em; // Espace pour les listes imbriquées
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
a {
|
|
151
|
+
color: $primary-color; // Couleur des liens
|
|
152
|
+
text-decoration: none;
|
|
153
|
+
font-weight: 500;
|
|
154
|
+
|
|
155
|
+
&:hover {
|
|
156
|
+
text-decoration: underline;
|
|
157
|
+
color: $primary-color-darker;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pre {
|
|
162
|
+
background-color: #2d2d2d; // Fond sombre pour les blocs de code
|
|
163
|
+
color: #f8f8f2; // Texte clair pour les blocs de code
|
|
164
|
+
padding: 1.2em 1.5em;
|
|
165
|
+
border-radius: 6px;
|
|
166
|
+
overflow-x: auto; // Permet le défilement horizontal si le code est trop long
|
|
167
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
168
|
+
font-size: 0.9em;
|
|
169
|
+
line-height: 1.6;
|
|
170
|
+
margin-bottom: 1.8rem;
|
|
171
|
+
white-space: pre-wrap; // Gère les retours à la ligne dans le code
|
|
172
|
+
word-wrap: break-word; // Coupe les mots longs si nécessaire
|
|
173
|
+
|
|
174
|
+
code {
|
|
175
|
+
background-color: transparent; // Pas de fond pour le code dans un <pre>
|
|
176
|
+
padding: 0;
|
|
177
|
+
color: inherit; // Hérite de la couleur du <pre>
|
|
178
|
+
font-size: 1em; // Taille normale dans le <pre>
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
blockquote {
|
|
183
|
+
border-left: 5px solid #dfe2e5; // Bordure pour les citations
|
|
184
|
+
padding: 10px 20px;
|
|
185
|
+
margin: 1.5em 0;
|
|
186
|
+
color: #586069; // Couleur de texte pour les citations
|
|
187
|
+
background-color: #f8f9fa; // Fond léger pour les citations
|
|
188
|
+
font-style: italic;
|
|
189
|
+
|
|
190
|
+
p {
|
|
191
|
+
margin-bottom: 0; // Pas de marge en bas pour le dernier paragraphe dans une citation
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
img {
|
|
196
|
+
max-width: 100%; // Assure que les images sont responsives
|
|
197
|
+
height: auto;
|
|
198
|
+
border-radius: 4px; // Coins arrondis pour les images
|
|
199
|
+
margin: 1em 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
hr {
|
|
203
|
+
border: 0;
|
|
204
|
+
height: 1px;
|
|
205
|
+
background-color: #e0e0e0;
|
|
206
|
+
margin: 2em 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Classes utilitaires que vous pourriez vouloir ajouter
|
|
210
|
+
.doc-note {
|
|
211
|
+
background-color: #fff3cd;
|
|
212
|
+
border-left: 4px solid #ffeeba;
|
|
213
|
+
padding: 15px;
|
|
214
|
+
margin: 1.5em 0;
|
|
215
|
+
border-radius: 4px;
|
|
216
|
+
color: #856404;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.doc-tip {
|
|
220
|
+
background-color: #d1ecf1;
|
|
221
|
+
border-left: 4px solid #bee5eb;
|
|
222
|
+
padding: 15px;
|
|
223
|
+
margin: 1.5em 0;
|
|
224
|
+
border-radius: 4px;
|
|
225
|
+
color: #0c5460;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.doc-warning {
|
|
229
|
+
background-color: #f8d7da;
|
|
230
|
+
border-left: 4px solid #f5c6cb;
|
|
231
|
+
padding: 15px;
|
|
232
|
+
margin: 1.5em 0;
|
|
233
|
+
border-radius: 4px;
|
|
234
|
+
color: #721c24;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@media only screen and (min-width: 1024Fpx){
|
|
240
|
+
.documentation-sidebar{
|
|
241
|
+
max-width: 360px; // Largeur fixe pour le menu latéral
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Styles responsives optionnels
|
|
245
|
+
@media (max-width: 768px) {
|
|
246
|
+
.documentation-layout {
|
|
247
|
+
flex-direction: column; // Empile le menu et le contenu sur petits écrans
|
|
248
|
+
height: auto; // Hauteur automatique pour permettre le défilement de toute la page
|
|
249
|
+
|
|
250
|
+
.documentation-sidebar {
|
|
251
|
+
width: 100%; // Prend toute la largeur
|
|
252
|
+
height: auto; // Hauteur basée sur le contenu
|
|
253
|
+
border-right: none;
|
|
254
|
+
border-bottom: 1px solid #dee2e6; // Bordure en bas à la place de droite
|
|
255
|
+
margin-bottom: 20px; // Espace avant le contenu principal
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.documentation-content {
|
|
259
|
+
padding: 20px; // Moins de padding sur petits écrans
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import './Draggable.scss'; // Fichier pour les styles (à créer)
|
|
3
|
+
|
|
4
|
+
function Draggable({ items: initialItems = [], renderItem, onChange }) {
|
|
5
|
+
const [items, setItems] = useState(initialItems);
|
|
6
|
+
const [draggedIndex, setDraggedIndex] = useState(null);
|
|
7
|
+
const [overIndex, setOverIndex] = useState(null);
|
|
8
|
+
|
|
9
|
+
const itemRefs = useRef([]); // Pour stocker les refs des éléments li
|
|
10
|
+
|
|
11
|
+
// Mettre à jour l'état interne si les items initiaux changent
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
setItems(initialItems);
|
|
14
|
+
// Rnitialiser les refs si la longueur change
|
|
15
|
+
itemRefs.current = itemRefs.current.slice(0, initialItems.length);
|
|
16
|
+
}, [initialItems]);
|
|
17
|
+
|
|
18
|
+
// --- Gestionnaires d'événements Drag & Drop (Bureau) ---
|
|
19
|
+
|
|
20
|
+
const handleDragStart = (e, index) => {
|
|
21
|
+
setDraggedIndex(index);
|
|
22
|
+
// Optionnel: Ajouter une classe pour le style de l'élément glissé
|
|
23
|
+
e.currentTarget.classList.add('dragging');
|
|
24
|
+
// Nécessaire pour Firefox
|
|
25
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
26
|
+
// On peut mettre l'ID ou l'index dans dataTransfer si besoin, mais on utilise l'état ici
|
|
27
|
+
e.dataTransfer.setData('text/plain', index);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleDragEnter = (e, index) => {
|
|
31
|
+
e.preventDefault(); // Important pour permettre le drop
|
|
32
|
+
if (index !== draggedIndex) {
|
|
33
|
+
setOverIndex(index); // Pour le style de la cible potentielle
|
|
34
|
+
|
|
35
|
+
// Logique de réorganisation immédiate (optionnel, pour feedback visuel)
|
|
36
|
+
// Décommenter si vous voulez voir l'élément bouger pendant le drag
|
|
37
|
+
/*
|
|
38
|
+
if (draggedIndex !== null) {
|
|
39
|
+
const newItems = [...items];
|
|
40
|
+
const [draggedItem] = newItems.splice(draggedIndex, 1);
|
|
41
|
+
newItems.splice(index, 0, draggedItem);
|
|
42
|
+
setItems(newItems);
|
|
43
|
+
setDraggedIndex(index); // Mettre à jour l'index aprs déplacement
|
|
44
|
+
}
|
|
45
|
+
*/
|
|
46
|
+
} else {
|
|
47
|
+
setOverIndex(null);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleDragOver = (e) => {
|
|
52
|
+
e.preventDefault(); // Doit être présent pour que onDrop fonctionne
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleDragLeave = (e) => {
|
|
56
|
+
// Optionnel: Réinitialiser overIndex si on quitte la zone sans dropper
|
|
57
|
+
// setOverIndex(null);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleDrop = (e, index) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
if (draggedIndex === null || draggedIndex === index) {
|
|
63
|
+
setOverIndex(null);
|
|
64
|
+
return; // Ne rien faire si on drop sur soi-même ou si rien n'est glissé
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const newItems = [...items];
|
|
68
|
+
const [draggedItem] = newItems.splice(draggedIndex, 1);
|
|
69
|
+
newItems.splice(index, 0, draggedItem);
|
|
70
|
+
|
|
71
|
+
setItems(newItems);
|
|
72
|
+
setOverIndex(null);
|
|
73
|
+
|
|
74
|
+
// Appeler le callback onChange avec la nouvelle liste triée
|
|
75
|
+
if (onChange) {
|
|
76
|
+
onChange(newItems);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleDragEnd = (e) => {
|
|
81
|
+
// Nettoyer l'état et les styles
|
|
82
|
+
e.currentTarget.classList.remove('dragging');
|
|
83
|
+
setDraggedIndex(null);
|
|
84
|
+
setOverIndex(null);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// --- Gestionnaires d'événements Tactiles (Mobile) ---
|
|
88
|
+
|
|
89
|
+
const touchStartY = useRef(0);
|
|
90
|
+
const currentTouchY = useRef(0);
|
|
91
|
+
const initialScrollTop = useRef(0); // Pour gérer le scroll pendant le drag tactile
|
|
92
|
+
|
|
93
|
+
const handleTouchStart = (e, index) => {
|
|
94
|
+
setDraggedIndex(index);
|
|
95
|
+
touchStartY.current = e.touches[0].clientY;
|
|
96
|
+
currentTouchY.current = touchStartY.current;
|
|
97
|
+
initialScrollTop.current = e.currentTarget.closest('.draggable-list')?.scrollTop || 0; // Stocker le scroll initial du conteneur
|
|
98
|
+
|
|
99
|
+
// Ajouter un style visuel
|
|
100
|
+
const target = itemRefs.current[index];
|
|
101
|
+
if (target) {
|
|
102
|
+
target.classList.add('dragging-touch');
|
|
103
|
+
}
|
|
104
|
+
// Empcher le scroll de la page pendant le drag de l'élément
|
|
105
|
+
// Attention: cela peut être trop agressif selon l'UX souhaitée
|
|
106
|
+
// document.body.style.overflow = 'hidden';
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleTouchMove = (e) => {
|
|
110
|
+
if (draggedIndex === null) return;
|
|
111
|
+
|
|
112
|
+
currentTouchY.current = e.touches[0].clientY;
|
|
113
|
+
const draggedElement = itemRefs.current[draggedIndex];
|
|
114
|
+
|
|
115
|
+
if (draggedElement) {
|
|
116
|
+
// Déplacer visuellement l'élément (simple translation pour le feedback)
|
|
117
|
+
// Une implémentation plus robuste utiliserait une copie ou un placeholder
|
|
118
|
+
const deltaY = currentTouchY.current - touchStartY.current;
|
|
119
|
+
draggedElement.style.transform = `translateY(${deltaY}px)`;
|
|
120
|
+
draggedElement.style.zIndex = '100'; // Mettre au-dessus des autres
|
|
121
|
+
|
|
122
|
+
// Déterminer sur quel élément on survole
|
|
123
|
+
let newOverIndex = null;
|
|
124
|
+
itemRefs.current.forEach((ref, index) => {
|
|
125
|
+
if (ref && index !== draggedIndex) {
|
|
126
|
+
const rect = ref.getBoundingClientRect();
|
|
127
|
+
// Si le centre vertical du doigt est dans les limites de l'élément
|
|
128
|
+
if (currentTouchY.current > rect.top && currentTouchY.current < rect.bottom) {
|
|
129
|
+
newOverIndex = index;
|
|
130
|
+
ref.classList.add('drag-over'); // Style pour la cible
|
|
131
|
+
} else {
|
|
132
|
+
ref.classList.remove('drag-over');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Si on survole un nouvel élément, mettre à jour l'état overIndex
|
|
138
|
+
if (newOverIndex !== null && newOverIndex !== overIndex) {
|
|
139
|
+
setOverIndex(newOverIndex);
|
|
140
|
+
} else if (newOverIndex === null) {
|
|
141
|
+
setOverIndex(null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- Auto-scroll (optionnel mais utile sur mobile) ---
|
|
145
|
+
const listElement = draggedElement.closest('.draggable-list');
|
|
146
|
+
if (listElement) {
|
|
147
|
+
const listRect = listElement.getBoundingClientRect();
|
|
148
|
+
const touchOffsetInList = currentTouchY.current - listRect.top;
|
|
149
|
+
const scrollThreshold = 50; // Pixels près du bord pour déclencher le scroll
|
|
150
|
+
|
|
151
|
+
if (touchOffsetInList < scrollThreshold) {
|
|
152
|
+
// Scroller vers le haut
|
|
153
|
+
listElement.scrollTop -= 10;
|
|
154
|
+
} else if (touchOffsetInList > listRect.height - scrollThreshold) {
|
|
155
|
+
// Scroller vers le bas
|
|
156
|
+
listElement.scrollTop += 10;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const handleTouchEnd = (e) => {
|
|
163
|
+
if (draggedIndex === null) return;
|
|
164
|
+
|
|
165
|
+
const draggedElement = itemRefs.current[draggedIndex];
|
|
166
|
+
if (draggedElement) {
|
|
167
|
+
// Réinitialiser le style visuel
|
|
168
|
+
draggedElement.style.transform = '';
|
|
169
|
+
draggedElement.style.zIndex = '';
|
|
170
|
+
draggedElement.classList.remove('dragging-touch');
|
|
171
|
+
}
|
|
172
|
+
itemRefs.current.forEach(ref => ref?.classList.remove('drag-over'));
|
|
173
|
+
// document.body.style.overflow = ''; // Ractile
|
|
174
|
+
if (overIndex !== null && overIndex !== draggedIndex) {
|
|
175
|
+
const newItems = [...items];
|
|
176
|
+
const [draggedItem] = newItems.splice(draggedIndex, 1);
|
|
177
|
+
newItems.splice(overIndex, 0, draggedItem);
|
|
178
|
+
|
|
179
|
+
setItems(newItems);
|
|
180
|
+
|
|
181
|
+
// Appeler le callback onChange
|
|
182
|
+
if (onChange) {
|
|
183
|
+
onChange(newItems);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Nettoyer l'état
|
|
188
|
+
setDraggedIndex(null);
|
|
189
|
+
setOverIndex(null);
|
|
190
|
+
touchStartY.current = 0;
|
|
191
|
+
currentTouchY.current = 0;
|
|
192
|
+
initialScrollTop.current = 0;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<ul className="draggable-list">
|
|
197
|
+
{items.map((item, index) => (
|
|
198
|
+
<li
|
|
199
|
+
key={item.id} // Assurez-vous que chaque item a un id unique
|
|
200
|
+
ref={el => itemRefs.current[index] = el} // Stocker la ref
|
|
201
|
+
draggable={true} // Activer le drag natif
|
|
202
|
+
onDragStart={(e) => handleDragStart(e, index)}
|
|
203
|
+
onDragEnter={(e) => handleDragEnter(e, index)}
|
|
204
|
+
onDragOver={handleDragOver}
|
|
205
|
+
onDragLeave={handleDragLeave}
|
|
206
|
+
onDrop={(e) => handleDrop(e, index)}
|
|
207
|
+
onDragEnd={handleDragEnd}
|
|
208
|
+
onTouchStart={(e) => handleTouchStart(e, index)}
|
|
209
|
+
onTouchMove={handleTouchMove}
|
|
210
|
+
onTouchEnd={handleTouchEnd}
|
|
211
|
+
className={`draggable-item ${index === overIndex ? 'drag-over' : ''}`}
|
|
212
|
+
style={{ opacity: index === draggedIndex ? 0.5 : 1 }} // Style simple pour l'élément glissé
|
|
213
|
+
>
|
|
214
|
+
{/* Rendre le contenu de l'item via la fonction renderItem */}
|
|
215
|
+
{renderItem ? renderItem(item, index) : JSON.stringify(item)}
|
|
216
|
+
</li>
|
|
217
|
+
))}
|
|
218
|
+
</ul>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export default Draggable;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.draggable-list {
|
|
2
|
+
list-style: none;
|
|
3
|
+
padding: 0;
|
|
4
|
+
margin: 0;
|
|
5
|
+
user-select: none; /* Empêche la sélection de texte pendant le drag */
|
|
6
|
+
overflow-y: auto; /* Important pour l'auto-scroll tactile */
|
|
7
|
+
// max-height: 400px; /* Définir une hauteur si nécessaire pour le scroll */
|
|
8
|
+
width: 100%;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.draggable-item {
|
|
12
|
+
padding: 8px 12px;
|
|
13
|
+
margin: 5px 0;
|
|
14
|
+
background-color: #f9f9f9;
|
|
15
|
+
border: 1px solid #eee;
|
|
16
|
+
cursor: grab;
|
|
17
|
+
transition: background-color 0.2s ease, opacity 0.2s ease, transform 0.1s linear; /* Transition pour transform (touch) */
|
|
18
|
+
position: relative; /* Pour z-index */
|
|
19
|
+
|
|
20
|
+
&:active {
|
|
21
|
+
cursor: grabbing;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Style pour l'élément en cours de drag (bureau)
|
|
25
|
+
&.dragging {
|
|
26
|
+
opacity: 0.5;
|
|
27
|
+
background-color: #e0e0e0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Style pour l'élément en cours de drag (tactile)
|
|
31
|
+
&.dragging-touch {
|
|
32
|
+
// background-color: #d3eaff; // Couleur différente si besoin
|
|
33
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
34
|
+
// opacity: 0.8; // Peut être redondant avec le style opacity inline
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Style pour la zone où l'élément peut être déposé
|
|
38
|
+
&.drag-over {
|
|
39
|
+
background-color: #d3eaff;
|
|
40
|
+
border-style: dashed;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Optionnel: Cacher l'élément fantôme natif pendant le drag (peut améliorer l'UX)
|
|
45
|
+
// .draggable-item.dragging {
|
|
46
|
+
// color: transparent; // Rend le texte invisible
|
|
47
|
+
// background: lightgrey; // Garde une forme visible
|
|
48
|
+
// }
|
|
49
|
+
// * Attention: Cacher complètement peut perturber le drag sur certains navigateurs.
|