canvasframework 0.3.6
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/README.md +554 -0
- package/components/Accordion.js +252 -0
- package/components/AndroidDatePickerDialog.js +398 -0
- package/components/AppBar.js +225 -0
- package/components/Avatar.js +202 -0
- package/components/BottomNavigationBar.js +205 -0
- package/components/BottomSheet.js +374 -0
- package/components/Button.js +225 -0
- package/components/Card.js +193 -0
- package/components/Checkbox.js +180 -0
- package/components/Chip.js +212 -0
- package/components/CircularProgress.js +143 -0
- package/components/ContextMenu.js +116 -0
- package/components/DatePicker.js +257 -0
- package/components/Dialog.js +367 -0
- package/components/Divider.js +125 -0
- package/components/Drawer.js +261 -0
- package/components/FAB.js +270 -0
- package/components/FileUpload.js +315 -0
- package/components/IOSDatePickerWheel.js +268 -0
- package/components/ImageCarousel.js +193 -0
- package/components/ImageComponent.js +223 -0
- package/components/Input.js +309 -0
- package/components/List.js +94 -0
- package/components/ListItem.js +223 -0
- package/components/Modal.js +364 -0
- package/components/MultiSelectDialog.js +206 -0
- package/components/NumberInput.js +271 -0
- package/components/ProgressBar.js +88 -0
- package/components/RadioButton.js +142 -0
- package/components/SearchInput.js +315 -0
- package/components/SegmentedControl.js +202 -0
- package/components/Select.js +199 -0
- package/components/SelectDialog.js +255 -0
- package/components/Slider.js +113 -0
- package/components/Snackbar.js +243 -0
- package/components/Stepper.js +281 -0
- package/components/SwipeableListItem.js +179 -0
- package/components/Switch.js +147 -0
- package/components/Table.js +492 -0
- package/components/Tabs.js +125 -0
- package/components/Text.js +141 -0
- package/components/TextField.js +331 -0
- package/components/Toast.js +236 -0
- package/components/TreeView.js +420 -0
- package/components/Video.js +397 -0
- package/components/View.js +140 -0
- package/components/VirtualList.js +120 -0
- package/core/CanvasFramework.js +1271 -0
- package/core/CanvasWork.js +32 -0
- package/core/Component.js +153 -0
- package/core/LogicWorker.js +25 -0
- package/core/WebGLCanvasAdapter.js +1369 -0
- package/features/Column.js +43 -0
- package/features/Grid.js +47 -0
- package/features/LayoutComponent.js +43 -0
- package/features/OpenStreetMap.js +310 -0
- package/features/Positioned.js +33 -0
- package/features/PullToRefresh.js +328 -0
- package/features/Row.js +40 -0
- package/features/SignaturePad.js +257 -0
- package/features/Skeleton.js +84 -0
- package/features/Stack.js +21 -0
- package/index.js +101 -0
- package/manager/AccessibilityManager.js +107 -0
- package/manager/ErrorHandler.js +59 -0
- package/manager/FeatureFlags.js +60 -0
- package/manager/MemoryManager.js +107 -0
- package/manager/PerformanceMonitor.js +84 -0
- package/manager/SecurityManager.js +54 -0
- package/package.json +28 -0
- package/utils/AnimationEngine.js +428 -0
- package/utils/DataStore.js +403 -0
- package/utils/EventBus.js +407 -0
- package/utils/FetchClient.js +74 -0
- package/utils/FormValidator.js +355 -0
- package/utils/GeoLocationService.js +62 -0
- package/utils/I18n.js +207 -0
- package/utils/IndexedDBManager.js +273 -0
- package/utils/OfflineSyncManager.js +342 -0
- package/utils/QueryBuilder.js +478 -0
- package/utils/SafeArea.js +64 -0
- package/utils/SecureStorage.js +289 -0
- package/utils/StateManager.js +207 -0
- package/utils/WebSocketClient.js +66 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gestionnaire IndexedDB pour stockage de grandes quantités de données
|
|
3
|
+
* @class
|
|
4
|
+
* @property {string} dbName - Nom de la base
|
|
5
|
+
* @property {number} version - Version
|
|
6
|
+
* @property {IDBDatabase} db - Instance DB
|
|
7
|
+
*/
|
|
8
|
+
class IndexedDBManager {
|
|
9
|
+
/**
|
|
10
|
+
* Crée une instance de IndexedDBManager
|
|
11
|
+
* @param {string} dbName - Nom de la base
|
|
12
|
+
* @param {number} [version=1] - Version
|
|
13
|
+
*/
|
|
14
|
+
constructor(dbName, version = 1) {
|
|
15
|
+
this.dbName = dbName;
|
|
16
|
+
this.version = version;
|
|
17
|
+
this.db = null;
|
|
18
|
+
this.stores = new Map();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialise la base de données
|
|
23
|
+
* @param {Object} schema - Schéma {storeName: {keyPath, indexes}}
|
|
24
|
+
* @returns {Promise<IndexedDBManager>} Instance
|
|
25
|
+
*/
|
|
26
|
+
async init(schema) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const request = indexedDB.open(this.dbName, this.version);
|
|
29
|
+
|
|
30
|
+
request.onerror = () => reject(request.error);
|
|
31
|
+
request.onsuccess = () => {
|
|
32
|
+
this.db = request.result;
|
|
33
|
+
resolve(this);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
request.onupgradeneeded = (event) => {
|
|
37
|
+
const db = event.target.result;
|
|
38
|
+
|
|
39
|
+
for (let storeName in schema) {
|
|
40
|
+
const config = schema[storeName];
|
|
41
|
+
|
|
42
|
+
// Créer l'object store s'il n'existe pas
|
|
43
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
44
|
+
const store = db.createObjectStore(storeName, {
|
|
45
|
+
keyPath: config.keyPath || 'id',
|
|
46
|
+
autoIncrement: config.autoIncrement || false
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Créer les indexes
|
|
50
|
+
if (config.indexes) {
|
|
51
|
+
for (let indexName in config.indexes) {
|
|
52
|
+
const indexConfig = config.indexes[indexName];
|
|
53
|
+
store.createIndex(
|
|
54
|
+
indexName,
|
|
55
|
+
indexConfig.keyPath || indexName,
|
|
56
|
+
{ unique: indexConfig.unique || false }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Ajoute un élément
|
|
68
|
+
* @param {string} storeName - Nom du store
|
|
69
|
+
* @param {*} item - Élément à ajouter
|
|
70
|
+
* @returns {Promise<*>} Clé générée
|
|
71
|
+
*/
|
|
72
|
+
async add(storeName, item) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const transaction = this.db.transaction([storeName], 'readwrite');
|
|
75
|
+
const store = transaction.objectStore(storeName);
|
|
76
|
+
const request = store.add(item);
|
|
77
|
+
|
|
78
|
+
request.onsuccess = () => resolve(request.result);
|
|
79
|
+
request.onerror = () => reject(request.error);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Met à jour un élément
|
|
85
|
+
* @param {string} storeName - Nom du store
|
|
86
|
+
* @param {*} item - Élément à mettre à jour
|
|
87
|
+
* @returns {Promise<*>} Clé
|
|
88
|
+
*/
|
|
89
|
+
async put(storeName, item) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const transaction = this.db.transaction([storeName], 'readwrite');
|
|
92
|
+
const store = transaction.objectStore(storeName);
|
|
93
|
+
const request = store.put(item);
|
|
94
|
+
|
|
95
|
+
request.onsuccess = () => resolve(request.result);
|
|
96
|
+
request.onerror = () => reject(request.error);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Récupère un élément par sa clé
|
|
102
|
+
* @param {string} storeName - Nom du store
|
|
103
|
+
* @param {*} key - Clé
|
|
104
|
+
* @returns {Promise<*>} Élément
|
|
105
|
+
*/
|
|
106
|
+
async get(storeName, key) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const transaction = this.db.transaction([storeName], 'readonly');
|
|
109
|
+
const store = transaction.objectStore(storeName);
|
|
110
|
+
const request = store.get(key);
|
|
111
|
+
|
|
112
|
+
request.onsuccess = () => resolve(request.result);
|
|
113
|
+
request.onerror = () => reject(request.error);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Récupère tous les éléments
|
|
119
|
+
* @param {string} storeName - Nom du store
|
|
120
|
+
* @returns {Promise<Array>} Liste des éléments
|
|
121
|
+
*/
|
|
122
|
+
async getAll(storeName) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const transaction = this.db.transaction([storeName], 'readonly');
|
|
125
|
+
const store = transaction.objectStore(storeName);
|
|
126
|
+
const request = store.getAll();
|
|
127
|
+
|
|
128
|
+
request.onsuccess = () => resolve(request.result);
|
|
129
|
+
request.onerror = () => reject(request.error);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Cherche par index
|
|
135
|
+
* @param {string} storeName - Nom du store
|
|
136
|
+
* @param {string} indexName - Nom de l'index
|
|
137
|
+
* @param {*} value - Valeur à chercher
|
|
138
|
+
* @returns {Promise<Array>} Résultats
|
|
139
|
+
*/
|
|
140
|
+
async getByIndex(storeName, indexName, value) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const transaction = this.db.transaction([storeName], 'readonly');
|
|
143
|
+
const store = transaction.objectStore(storeName);
|
|
144
|
+
const index = store.index(indexName);
|
|
145
|
+
const request = index.getAll(value);
|
|
146
|
+
|
|
147
|
+
request.onsuccess = () => resolve(request.result);
|
|
148
|
+
request.onerror = () => reject(request.error);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Query avec filtre
|
|
154
|
+
* @param {string} storeName - Nom du store
|
|
155
|
+
* @param {Function} predicate - Fonction de filtre
|
|
156
|
+
* @returns {Promise<Array>} Résultats
|
|
157
|
+
*/
|
|
158
|
+
async query(storeName, predicate) {
|
|
159
|
+
const all = await this.getAll(storeName);
|
|
160
|
+
return all.filter(predicate);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Supprime un élément
|
|
165
|
+
* @param {string} storeName - Nom du store
|
|
166
|
+
* @param {*} key - Clé
|
|
167
|
+
* @returns {Promise<void>}
|
|
168
|
+
*/
|
|
169
|
+
async delete(storeName, key) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const transaction = this.db.transaction([storeName], 'readwrite');
|
|
172
|
+
const store = transaction.objectStore(storeName);
|
|
173
|
+
const request = store.delete(key);
|
|
174
|
+
|
|
175
|
+
request.onsuccess = () => resolve();
|
|
176
|
+
request.onerror = () => reject(request.error);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Vide un store
|
|
182
|
+
* @param {string} storeName - Nom du store
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
async clear(storeName) {
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
const transaction = this.db.transaction([storeName], 'readwrite');
|
|
188
|
+
const store = transaction.objectStore(storeName);
|
|
189
|
+
const request = store.clear();
|
|
190
|
+
|
|
191
|
+
request.onsuccess = () => resolve();
|
|
192
|
+
request.onerror = () => reject(request.error);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Compte les éléments
|
|
198
|
+
* @param {string} storeName - Nom du store
|
|
199
|
+
* @returns {Promise<number>} Nombre d'éléments
|
|
200
|
+
*/
|
|
201
|
+
async count(storeName) {
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
const transaction = this.db.transaction([storeName], 'readonly');
|
|
204
|
+
const store = transaction.objectStore(storeName);
|
|
205
|
+
const request = store.count();
|
|
206
|
+
|
|
207
|
+
request.onsuccess = () => resolve(request.result);
|
|
208
|
+
request.onerror = () => reject(request.error);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Ajoute plusieurs éléments (batch)
|
|
214
|
+
* @param {string} storeName - Nom du store
|
|
215
|
+
* @param {Array} items - Éléments
|
|
216
|
+
* @returns {Promise<void>}
|
|
217
|
+
*/
|
|
218
|
+
async bulkAdd(storeName, items) {
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
const transaction = this.db.transaction([storeName], 'readwrite');
|
|
221
|
+
const store = transaction.objectStore(storeName);
|
|
222
|
+
|
|
223
|
+
items.forEach(item => store.add(item));
|
|
224
|
+
|
|
225
|
+
transaction.oncomplete = () => resolve();
|
|
226
|
+
transaction.onerror = () => reject(transaction.error);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Supprime plusieurs éléments
|
|
232
|
+
* @param {string} storeName - Nom du store
|
|
233
|
+
* @param {Array} keys - Clés
|
|
234
|
+
* @returns {Promise<void>}
|
|
235
|
+
*/
|
|
236
|
+
async bulkDelete(storeName, keys) {
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
const transaction = this.db.transaction([storeName], 'readwrite');
|
|
239
|
+
const store = transaction.objectStore(storeName);
|
|
240
|
+
|
|
241
|
+
keys.forEach(key => store.delete(key));
|
|
242
|
+
|
|
243
|
+
transaction.oncomplete = () => resolve();
|
|
244
|
+
transaction.onerror = () => reject(transaction.error);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Ferme la connexion
|
|
250
|
+
*/
|
|
251
|
+
close() {
|
|
252
|
+
if (this.db) {
|
|
253
|
+
this.db.close();
|
|
254
|
+
this.db = null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Supprime la base de données
|
|
260
|
+
* @returns {Promise<void>}
|
|
261
|
+
*/
|
|
262
|
+
async destroy() {
|
|
263
|
+
this.close();
|
|
264
|
+
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
const request = indexedDB.deleteDatabase(this.dbName);
|
|
267
|
+
request.onsuccess = () => resolve();
|
|
268
|
+
request.onerror = () => reject(request.error);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export default IndexedDBManager;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gestionnaire de synchronisation hors ligne
|
|
3
|
+
* Queue les opérations offline et les sync quand online
|
|
4
|
+
* @class
|
|
5
|
+
* @property {Array} queue - File d'attente des opérations
|
|
6
|
+
* @property {boolean} isOnline - État de connexion
|
|
7
|
+
* @property {Map} syncHandlers - Handlers de sync par type
|
|
8
|
+
*/
|
|
9
|
+
class OfflineSyncManager {
|
|
10
|
+
/**
|
|
11
|
+
* Crée une instance de OfflineSyncManager
|
|
12
|
+
* @param {Object} [options={}] - Options
|
|
13
|
+
* @param {string} [options.storageKey='offline_queue'] - Clé de stockage
|
|
14
|
+
* @param {number} [options.retryDelay=5000] - Délai entre tentatives (ms)
|
|
15
|
+
* @param {number} [options.maxRetries=3] - Tentatives max
|
|
16
|
+
*/
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.storageKey = options.storageKey || 'offline_queue';
|
|
19
|
+
this.retryDelay = options.retryDelay || 5000;
|
|
20
|
+
this.maxRetries = options.maxRetries || 3;
|
|
21
|
+
|
|
22
|
+
this.queue = [];
|
|
23
|
+
this.isOnline = navigator.onLine;
|
|
24
|
+
this.syncHandlers = new Map();
|
|
25
|
+
this.isSyncing = false;
|
|
26
|
+
|
|
27
|
+
// Charger la queue sauvegardée
|
|
28
|
+
this.loadQueue();
|
|
29
|
+
|
|
30
|
+
// Écouter les changements de connexion
|
|
31
|
+
this.setupConnectionListeners();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configure les listeners de connexion
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
setupConnectionListeners() {
|
|
39
|
+
window.addEventListener('online', () => {
|
|
40
|
+
console.log('🟢 Connection restored');
|
|
41
|
+
this.isOnline = true;
|
|
42
|
+
this.syncAll();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
window.addEventListener('offline', () => {
|
|
46
|
+
console.log('🔴 Connection lost');
|
|
47
|
+
this.isOnline = false;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Enregistre un handler de sync
|
|
53
|
+
* @param {string} type - Type d'opération
|
|
54
|
+
* @param {Function} handler - Handler async (operation) => Promise
|
|
55
|
+
*/
|
|
56
|
+
registerSyncHandler(type, handler) {
|
|
57
|
+
this.syncHandlers.set(type, handler);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Ajoute une opération à la queue
|
|
62
|
+
* @param {string} type - Type d'opération
|
|
63
|
+
* @param {Object} data - Données
|
|
64
|
+
* @param {Object} [options={}] - Options
|
|
65
|
+
* @returns {Promise<*>} Résultat
|
|
66
|
+
*/
|
|
67
|
+
async queue(type, data, options = {}) {
|
|
68
|
+
const operation = {
|
|
69
|
+
id: this.generateId(),
|
|
70
|
+
type,
|
|
71
|
+
data,
|
|
72
|
+
timestamp: Date.now(),
|
|
73
|
+
retries: 0,
|
|
74
|
+
status: 'pending',
|
|
75
|
+
...options
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Si online, essayer d'exécuter directement
|
|
79
|
+
if (this.isOnline && !options.forceQueue) {
|
|
80
|
+
try {
|
|
81
|
+
const result = await this.executeOperation(operation);
|
|
82
|
+
return result;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn('Failed to execute immediately, queuing...', error);
|
|
85
|
+
// Continuer pour ajouter à la queue
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Ajouter à la queue
|
|
90
|
+
this.queue.push(operation);
|
|
91
|
+
this.saveQueue();
|
|
92
|
+
|
|
93
|
+
console.log(`📋 Operation queued: ${type}`, operation.id);
|
|
94
|
+
|
|
95
|
+
// Essayer de sync immédiatement si online
|
|
96
|
+
if (this.isOnline) {
|
|
97
|
+
this.syncAll();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return operation;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Exécute une opération
|
|
105
|
+
* @param {Object} operation - Opération
|
|
106
|
+
* @returns {Promise<*>} Résultat
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
async executeOperation(operation) {
|
|
110
|
+
const handler = this.syncHandlers.get(operation.type);
|
|
111
|
+
|
|
112
|
+
if (!handler) {
|
|
113
|
+
throw new Error(`No sync handler for type: ${operation.type}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return await handler(operation.data);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Synchronise toutes les opérations en attente
|
|
121
|
+
* @returns {Promise<Object>} Résultats
|
|
122
|
+
*/
|
|
123
|
+
async syncAll() {
|
|
124
|
+
if (this.isSyncing || !this.isOnline) return;
|
|
125
|
+
|
|
126
|
+
this.isSyncing = true;
|
|
127
|
+
console.log('🔄 Starting sync...', this.queue.length, 'operations');
|
|
128
|
+
|
|
129
|
+
const results = {
|
|
130
|
+
success: [],
|
|
131
|
+
failed: [],
|
|
132
|
+
skipped: []
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Trier par timestamp (FIFO)
|
|
136
|
+
this.queue.sort((a, b) => a.timestamp - b.timestamp);
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
139
|
+
const operation = this.queue[i];
|
|
140
|
+
|
|
141
|
+
if (operation.status === 'success') {
|
|
142
|
+
results.skipped.push(operation);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
console.log(`⏳ Syncing: ${operation.type}`, operation.id);
|
|
148
|
+
|
|
149
|
+
const result = await this.executeOperation(operation);
|
|
150
|
+
|
|
151
|
+
operation.status = 'success';
|
|
152
|
+
operation.result = result;
|
|
153
|
+
operation.syncedAt = Date.now();
|
|
154
|
+
|
|
155
|
+
results.success.push(operation);
|
|
156
|
+
console.log(`✅ Synced: ${operation.type}`, operation.id);
|
|
157
|
+
|
|
158
|
+
} catch (error) {
|
|
159
|
+
operation.retries++;
|
|
160
|
+
operation.lastError = error.message;
|
|
161
|
+
|
|
162
|
+
if (operation.retries >= this.maxRetries) {
|
|
163
|
+
operation.status = 'failed';
|
|
164
|
+
results.failed.push(operation);
|
|
165
|
+
console.error(`❌ Failed permanently: ${operation.type}`, operation.id);
|
|
166
|
+
} else {
|
|
167
|
+
operation.status = 'pending';
|
|
168
|
+
console.warn(`⚠️ Retry ${operation.retries}/${this.maxRetries}:`, operation.type);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Supprimer les opérations réussies de la queue
|
|
174
|
+
this.queue = this.queue.filter(op => op.status !== 'success');
|
|
175
|
+
this.saveQueue();
|
|
176
|
+
|
|
177
|
+
this.isSyncing = false;
|
|
178
|
+
|
|
179
|
+
console.log('✅ Sync complete:', results);
|
|
180
|
+
|
|
181
|
+
// Réessayer les échecs après un délai
|
|
182
|
+
if (results.failed.length > 0 || this.queue.length > 0) {
|
|
183
|
+
setTimeout(() => this.syncAll(), this.retryDelay);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return results;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Synchronise un type spécifique
|
|
191
|
+
* @param {string} type - Type d'opération
|
|
192
|
+
* @returns {Promise<Object>} Résultats
|
|
193
|
+
*/
|
|
194
|
+
async syncType(type) {
|
|
195
|
+
const operations = this.queue.filter(op => op.type === type && op.status !== 'success');
|
|
196
|
+
|
|
197
|
+
const results = {
|
|
198
|
+
success: [],
|
|
199
|
+
failed: []
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (let operation of operations) {
|
|
203
|
+
try {
|
|
204
|
+
const result = await this.executeOperation(operation);
|
|
205
|
+
operation.status = 'success';
|
|
206
|
+
operation.result = result;
|
|
207
|
+
results.success.push(operation);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
operation.retries++;
|
|
210
|
+
operation.lastError = error.message;
|
|
211
|
+
|
|
212
|
+
if (operation.retries >= this.maxRetries) {
|
|
213
|
+
operation.status = 'failed';
|
|
214
|
+
results.failed.push(operation);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.queue = this.queue.filter(op => op.status !== 'success');
|
|
220
|
+
this.saveQueue();
|
|
221
|
+
|
|
222
|
+
return results;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Obtient les opérations en attente
|
|
227
|
+
* @param {string} [type] - Filtrer par type
|
|
228
|
+
* @returns {Array} Opérations
|
|
229
|
+
*/
|
|
230
|
+
getPending(type) {
|
|
231
|
+
if (type) {
|
|
232
|
+
return this.queue.filter(op => op.type === type && op.status === 'pending');
|
|
233
|
+
}
|
|
234
|
+
return this.queue.filter(op => op.status === 'pending');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Obtient les opérations échouées
|
|
239
|
+
* @returns {Array} Opérations
|
|
240
|
+
*/
|
|
241
|
+
getFailed() {
|
|
242
|
+
return this.queue.filter(op => op.status === 'failed');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Supprime une opération
|
|
247
|
+
* @param {string} id - ID de l'opération
|
|
248
|
+
* @returns {boolean} True si supprimé
|
|
249
|
+
*/
|
|
250
|
+
remove(id) {
|
|
251
|
+
const index = this.queue.findIndex(op => op.id === id);
|
|
252
|
+
|
|
253
|
+
if (index > -1) {
|
|
254
|
+
this.queue.splice(index, 1);
|
|
255
|
+
this.saveQueue();
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Réinitialise une opération échouée
|
|
264
|
+
* @param {string} id - ID de l'opération
|
|
265
|
+
*/
|
|
266
|
+
retry(id) {
|
|
267
|
+
const operation = this.queue.find(op => op.id === id);
|
|
268
|
+
|
|
269
|
+
if (operation) {
|
|
270
|
+
operation.status = 'pending';
|
|
271
|
+
operation.retries = 0;
|
|
272
|
+
operation.lastError = null;
|
|
273
|
+
this.saveQueue();
|
|
274
|
+
|
|
275
|
+
if (this.isOnline) {
|
|
276
|
+
this.syncAll();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Vide la queue
|
|
283
|
+
*/
|
|
284
|
+
clear() {
|
|
285
|
+
this.queue = [];
|
|
286
|
+
this.saveQueue();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Sauvegarde la queue
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
saveQueue() {
|
|
294
|
+
try {
|
|
295
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.queue));
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error('Failed to save queue:', error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Charge la queue
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
loadQueue() {
|
|
306
|
+
try {
|
|
307
|
+
const saved = localStorage.getItem(this.storageKey);
|
|
308
|
+
if (saved) {
|
|
309
|
+
this.queue = JSON.parse(saved);
|
|
310
|
+
console.log('📋 Loaded queue:', this.queue.length, 'operations');
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error('Failed to load queue:', error);
|
|
314
|
+
this.queue = [];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Génère un ID unique
|
|
320
|
+
* @returns {string} ID
|
|
321
|
+
* @private
|
|
322
|
+
*/
|
|
323
|
+
generateId() {
|
|
324
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Obtient les statistiques
|
|
329
|
+
* @returns {Object} Stats
|
|
330
|
+
*/
|
|
331
|
+
getStats() {
|
|
332
|
+
return {
|
|
333
|
+
total: this.queue.length,
|
|
334
|
+
pending: this.queue.filter(op => op.status === 'pending').length,
|
|
335
|
+
failed: this.queue.filter(op => op.status === 'failed').length,
|
|
336
|
+
isOnline: this.isOnline,
|
|
337
|
+
isSyncing: this.isSyncing
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export default OfflineSyncManager;
|