@letar/forms 1.0.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/CHANGELOG.md +83 -0
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/chunk-G3HYXHCZ.js +435 -0
- package/chunk-G3HYXHCZ.js.map +1 -0
- package/chunk-GIBNEYK3.js +191 -0
- package/chunk-GIBNEYK3.js.map +1 -0
- package/i18n.js +3 -0
- package/i18n.js.map +1 -0
- package/index.js +7842 -0
- package/index.js.map +1 -0
- package/offline.js +3 -0
- package/offline.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { useSyncExternalStore, useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { Badge, HStack, Icon, Spinner } from '@chakra-ui/react';
|
|
3
|
+
import { LuWifiOff, LuClock, LuCheck } from 'react-icons/lu';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/lib/offline/offline-service.ts
|
|
7
|
+
function canUseIDB() {
|
|
8
|
+
return typeof window !== "undefined" && typeof indexedDB !== "undefined";
|
|
9
|
+
}
|
|
10
|
+
var idbModule = null;
|
|
11
|
+
async function getIDB() {
|
|
12
|
+
if (!canUseIDB()) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (!idbModule) {
|
|
16
|
+
idbModule = await import('idb-keyval');
|
|
17
|
+
}
|
|
18
|
+
return idbModule;
|
|
19
|
+
}
|
|
20
|
+
var DEFAULT_SYNC_QUEUE_STORAGE_KEY = "lena-form-sync-queue";
|
|
21
|
+
function getOfflineStatus() {
|
|
22
|
+
if (typeof navigator === "undefined") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return !navigator.onLine;
|
|
26
|
+
}
|
|
27
|
+
function subscribeToStatusChanges(callback) {
|
|
28
|
+
if (typeof window === "undefined") {
|
|
29
|
+
return () => {
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const handleOnline = () => callback(false);
|
|
33
|
+
const handleOffline = () => callback(true);
|
|
34
|
+
window.addEventListener("online", handleOnline);
|
|
35
|
+
window.addEventListener("offline", handleOffline);
|
|
36
|
+
return () => {
|
|
37
|
+
window.removeEventListener("online", handleOnline);
|
|
38
|
+
window.removeEventListener("offline", handleOffline);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function generateId() {
|
|
42
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
43
|
+
}
|
|
44
|
+
async function getQueueFromStorage(storageKey) {
|
|
45
|
+
try {
|
|
46
|
+
const idb = await getIDB();
|
|
47
|
+
if (!idb) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY;
|
|
51
|
+
const stored = await idb.get(key);
|
|
52
|
+
return stored ?? [];
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("[OfflineService] \u041E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438\u0437 IndexedDB:", error);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function saveQueueToStorage(queue, storageKey) {
|
|
59
|
+
try {
|
|
60
|
+
const idb = await getIDB();
|
|
61
|
+
if (!idb) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY;
|
|
65
|
+
await idb.set(key, queue);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("[OfflineService] \u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0432 IndexedDB:", error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function addToQueue(action, storageKey) {
|
|
71
|
+
const queue = await getQueueFromStorage(storageKey);
|
|
72
|
+
const item = {
|
|
73
|
+
id: generateId(),
|
|
74
|
+
action,
|
|
75
|
+
createdAt: Date.now(),
|
|
76
|
+
attempts: 0,
|
|
77
|
+
maxAttempts: 3,
|
|
78
|
+
status: "PENDING"
|
|
79
|
+
};
|
|
80
|
+
queue.push(item);
|
|
81
|
+
await saveQueueToStorage(queue, storageKey);
|
|
82
|
+
return item;
|
|
83
|
+
}
|
|
84
|
+
async function removeFromQueue(id, storageKey) {
|
|
85
|
+
const queue = await getQueueFromStorage(storageKey);
|
|
86
|
+
const index = queue.findIndex((item) => item.id === id);
|
|
87
|
+
if (index === -1) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
queue.splice(index, 1);
|
|
91
|
+
await saveQueueToStorage(queue, storageKey);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
async function processQueueItem(item, handler) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await handler(item.action);
|
|
97
|
+
if (result.success) {
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
item: { ...item, status: "SYNCED" }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const updatedItem = {
|
|
104
|
+
...item,
|
|
105
|
+
attempts: item.attempts + 1,
|
|
106
|
+
status: item.attempts + 1 >= item.maxAttempts ? "FAILED" : "PENDING",
|
|
107
|
+
error: result.error
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
item: updatedItem,
|
|
112
|
+
error: result.error
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
116
|
+
const updatedItem = {
|
|
117
|
+
...item,
|
|
118
|
+
attempts: item.attempts + 1,
|
|
119
|
+
status: item.attempts + 1 >= item.maxAttempts ? "FAILED" : "PENDING",
|
|
120
|
+
error: errorMessage
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
item: updatedItem,
|
|
125
|
+
error: errorMessage
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function clearQueue(storageKey) {
|
|
130
|
+
try {
|
|
131
|
+
const idb = await getIDB();
|
|
132
|
+
if (!idb) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY;
|
|
136
|
+
await idb.del(key);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("[OfflineService] \u041E\u0448\u0438\u0431\u043A\u0430 \u043E\u0447\u0438\u0441\u0442\u043A\u0438 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438\u0437 IndexedDB:", error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function createSyncQueueStore(storageKey) {
|
|
142
|
+
let queue = [];
|
|
143
|
+
const listeners2 = /* @__PURE__ */ new Set();
|
|
144
|
+
const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY;
|
|
145
|
+
const notifyListeners2 = () => {
|
|
146
|
+
listeners2.forEach((listener) => listener());
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
getQueue: () => queue,
|
|
150
|
+
getQueueLength: () => queue.length,
|
|
151
|
+
subscribe: (listener) => {
|
|
152
|
+
listeners2.add(listener);
|
|
153
|
+
return () => {
|
|
154
|
+
listeners2.delete(listener);
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
initialize: async () => {
|
|
158
|
+
queue = await getQueueFromStorage(key);
|
|
159
|
+
notifyListeners2();
|
|
160
|
+
},
|
|
161
|
+
add: async (action) => {
|
|
162
|
+
const item = await addToQueue(action, key);
|
|
163
|
+
queue.push(item);
|
|
164
|
+
notifyListeners2();
|
|
165
|
+
return item;
|
|
166
|
+
},
|
|
167
|
+
remove: async (id) => {
|
|
168
|
+
const result = await removeFromQueue(id, key);
|
|
169
|
+
if (result) {
|
|
170
|
+
queue = queue.filter((item) => item.id !== id);
|
|
171
|
+
notifyListeners2();
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
},
|
|
175
|
+
processAll: async (handler) => {
|
|
176
|
+
const results = [];
|
|
177
|
+
for (const item of queue) {
|
|
178
|
+
if (item.status === "PENDING") {
|
|
179
|
+
const result = await processQueueItem(item, handler);
|
|
180
|
+
results.push(result);
|
|
181
|
+
if (result.success && result.item) {
|
|
182
|
+
await removeFromQueue(item.id, key);
|
|
183
|
+
queue = queue.filter((q) => q.id !== item.id);
|
|
184
|
+
} else if (result.item) {
|
|
185
|
+
const index = queue.findIndex((q) => q.id === item.id);
|
|
186
|
+
if (index !== -1) {
|
|
187
|
+
queue[index] = result.item;
|
|
188
|
+
}
|
|
189
|
+
await saveQueueToStorage(queue, key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
notifyListeners2();
|
|
194
|
+
return results;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
var isOffline = false;
|
|
199
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
200
|
+
var notifyListeners = () => {
|
|
201
|
+
listeners.forEach((listener) => listener());
|
|
202
|
+
};
|
|
203
|
+
if (typeof window !== "undefined") {
|
|
204
|
+
isOffline = getOfflineStatus();
|
|
205
|
+
subscribeToStatusChanges((offline) => {
|
|
206
|
+
isOffline = offline;
|
|
207
|
+
notifyListeners();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function useOfflineStatus() {
|
|
211
|
+
return useSyncExternalStore(
|
|
212
|
+
(callback) => {
|
|
213
|
+
listeners.add(callback);
|
|
214
|
+
return () => {
|
|
215
|
+
listeners.delete(callback);
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
() => isOffline,
|
|
219
|
+
() => false
|
|
220
|
+
// SSR fallback — считаем что онлайн
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
var defaultSyncQueueStore = createSyncQueueStore();
|
|
224
|
+
var EMPTY_QUEUE = [];
|
|
225
|
+
var initialized = false;
|
|
226
|
+
var initialize = async () => {
|
|
227
|
+
if (!initialized && typeof window !== "undefined") {
|
|
228
|
+
initialized = true;
|
|
229
|
+
await defaultSyncQueueStore.initialize();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
function useSyncQueue() {
|
|
233
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
234
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
235
|
+
const isOffline2 = useOfflineStatus();
|
|
236
|
+
const queue = useSyncExternalStore(
|
|
237
|
+
(callback) => defaultSyncQueueStore.subscribe(callback),
|
|
238
|
+
() => defaultSyncQueueStore.getQueue(),
|
|
239
|
+
() => EMPTY_QUEUE
|
|
240
|
+
// SSR fallback
|
|
241
|
+
);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
initialize().then(() => {
|
|
244
|
+
setIsLoading(false);
|
|
245
|
+
});
|
|
246
|
+
}, []);
|
|
247
|
+
const addAction = useCallback(async (action) => {
|
|
248
|
+
return defaultSyncQueueStore.add(action);
|
|
249
|
+
}, []);
|
|
250
|
+
const removeAction = useCallback(async (id) => {
|
|
251
|
+
return defaultSyncQueueStore.remove(id);
|
|
252
|
+
}, []);
|
|
253
|
+
const processQueue = useCallback(
|
|
254
|
+
async (handler) => {
|
|
255
|
+
if (isOffline2) {
|
|
256
|
+
console.warn("[SyncQueue] \u041D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0432 \u043E\u0444\u0444\u043B\u0430\u0439\u043D \u0440\u0435\u0436\u0438\u043C\u0435");
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
setIsProcessing(true);
|
|
260
|
+
try {
|
|
261
|
+
return await defaultSyncQueueStore.processAll(handler);
|
|
262
|
+
} finally {
|
|
263
|
+
setIsProcessing(false);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
[isOffline2]
|
|
267
|
+
);
|
|
268
|
+
return {
|
|
269
|
+
queue,
|
|
270
|
+
queueLength: queue.length,
|
|
271
|
+
pendingCount: queue.filter((item) => item.status === "PENDING").length,
|
|
272
|
+
isLoading,
|
|
273
|
+
isProcessing,
|
|
274
|
+
addAction,
|
|
275
|
+
removeAction,
|
|
276
|
+
processQueue
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/lib/offline/use-offline-form.ts
|
|
281
|
+
function useOfflineForm({
|
|
282
|
+
actionType,
|
|
283
|
+
onlineSubmit,
|
|
284
|
+
onSuccess,
|
|
285
|
+
onQueued,
|
|
286
|
+
onError
|
|
287
|
+
}) {
|
|
288
|
+
const isOffline2 = useOfflineStatus();
|
|
289
|
+
const { addAction, processQueue, pendingCount, isProcessing, queueLength } = useSyncQueue();
|
|
290
|
+
const [lastSyncAttempt, setLastSyncAttempt] = useState(null);
|
|
291
|
+
const processingRef = useRef(false);
|
|
292
|
+
const submit = useCallback(
|
|
293
|
+
async (value) => {
|
|
294
|
+
if (isOffline2) {
|
|
295
|
+
try {
|
|
296
|
+
const queueItem = await addAction({
|
|
297
|
+
type: actionType,
|
|
298
|
+
payload: value
|
|
299
|
+
});
|
|
300
|
+
onQueued?.();
|
|
301
|
+
return {
|
|
302
|
+
success: true,
|
|
303
|
+
queued: true,
|
|
304
|
+
queueItemId: queueItem.id
|
|
305
|
+
};
|
|
306
|
+
} catch (error) {
|
|
307
|
+
const errorMessage = error instanceof Error ? error.message : "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u044C";
|
|
308
|
+
onError?.(errorMessage);
|
|
309
|
+
return {
|
|
310
|
+
success: false,
|
|
311
|
+
error: errorMessage
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const result = await onlineSubmit(value);
|
|
317
|
+
if (result.success) {
|
|
318
|
+
onSuccess?.();
|
|
319
|
+
} else if (result.error) {
|
|
320
|
+
onError?.(result.error);
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
success: result.success,
|
|
324
|
+
error: result.error,
|
|
325
|
+
queued: false
|
|
326
|
+
};
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const errorMessage = error instanceof Error ? error.message : "\u041E\u0448\u0438\u0431\u043A\u0430 \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0438";
|
|
329
|
+
onError?.(errorMessage);
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
error: errorMessage,
|
|
333
|
+
queued: false
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
[isOffline2, actionType, addAction, onlineSubmit, onSuccess, onQueued, onError]
|
|
338
|
+
);
|
|
339
|
+
const handleQueuedAction = useCallback(
|
|
340
|
+
async (action) => {
|
|
341
|
+
if (action.type !== actionType) {
|
|
342
|
+
return { success: true };
|
|
343
|
+
}
|
|
344
|
+
return onlineSubmit(action.payload);
|
|
345
|
+
},
|
|
346
|
+
[actionType, onlineSubmit]
|
|
347
|
+
);
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (!isOffline2 && pendingCount > 0 && !processingRef.current) {
|
|
350
|
+
processingRef.current = true;
|
|
351
|
+
setLastSyncAttempt(Date.now());
|
|
352
|
+
processQueue(handleQueuedAction).then((results) => {
|
|
353
|
+
const failed = results.filter((r) => !r.success);
|
|
354
|
+
if (failed.length > 0) {
|
|
355
|
+
console.warn(`[OfflineForm] ${failed.length} \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u0438\u043D\u0445\u0440\u043E\u043D\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C`);
|
|
356
|
+
}
|
|
357
|
+
}).finally(() => {
|
|
358
|
+
processingRef.current = false;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}, [isOffline2, pendingCount, processQueue, handleQueuedAction]);
|
|
362
|
+
return {
|
|
363
|
+
submit,
|
|
364
|
+
isOffline: isOffline2,
|
|
365
|
+
pendingCount,
|
|
366
|
+
queueLength,
|
|
367
|
+
isProcessing,
|
|
368
|
+
lastSyncAttempt
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function FormOfflineIndicator({
|
|
372
|
+
label = "\u041E\u0444\u0444\u043B\u0430\u0439\u043D \u0440\u0435\u0436\u0438\u043C",
|
|
373
|
+
colorPalette = "orange",
|
|
374
|
+
variant = "subtle",
|
|
375
|
+
...rest
|
|
376
|
+
}) {
|
|
377
|
+
const isOffline2 = useOfflineStatus();
|
|
378
|
+
if (!isOffline2) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
return /* @__PURE__ */ jsx(Badge, { colorPalette, variant, "data-testid": "offline-indicator", ...rest, children: /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
|
|
382
|
+
/* @__PURE__ */ jsx(Icon, { asChild: true, boxSize: 3, children: /* @__PURE__ */ jsx(LuWifiOff, {}) }),
|
|
383
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
384
|
+
] }) });
|
|
385
|
+
}
|
|
386
|
+
function FormSyncStatus({
|
|
387
|
+
showWhenEmpty = false,
|
|
388
|
+
syncingLabel = "\u0421\u0438\u043D\u0445\u0440\u043E\u043D\u0438\u0437\u0430\u0446\u0438\u044F...",
|
|
389
|
+
pendingLabel = (count) => `\u041E\u0436\u0438\u0434\u0430\u0435\u0442: ${count}`,
|
|
390
|
+
syncedLabel = "\u0421\u0438\u043D\u0445\u0440\u043E\u043D\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E",
|
|
391
|
+
colorPalette = "blue",
|
|
392
|
+
...rest
|
|
393
|
+
}) {
|
|
394
|
+
const isOffline2 = useOfflineStatus();
|
|
395
|
+
const { pendingCount, isProcessing } = useSyncQueue();
|
|
396
|
+
if (!isOffline2 && pendingCount === 0 && !isProcessing && !showWhenEmpty) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const renderContent = () => {
|
|
400
|
+
if (isProcessing) {
|
|
401
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
|
|
402
|
+
/* @__PURE__ */ jsx(Spinner, { size: "xs" }),
|
|
403
|
+
/* @__PURE__ */ jsx("span", { children: syncingLabel })
|
|
404
|
+
] });
|
|
405
|
+
}
|
|
406
|
+
if (pendingCount > 0) {
|
|
407
|
+
const label = typeof pendingLabel === "function" ? pendingLabel(pendingCount) : pendingLabel;
|
|
408
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
|
|
409
|
+
/* @__PURE__ */ jsx(Icon, { asChild: true, boxSize: 3, children: /* @__PURE__ */ jsx(LuClock, {}) }),
|
|
410
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
411
|
+
] });
|
|
412
|
+
}
|
|
413
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: 1, children: [
|
|
414
|
+
/* @__PURE__ */ jsx(Icon, { asChild: true, boxSize: 3, children: /* @__PURE__ */ jsx(LuCheck, {}) }),
|
|
415
|
+
/* @__PURE__ */ jsx("span", { children: syncedLabel })
|
|
416
|
+
] });
|
|
417
|
+
};
|
|
418
|
+
const effectiveColorPalette = pendingCount > 0 ? "orange" : isProcessing ? colorPalette : "green";
|
|
419
|
+
return /* @__PURE__ */ jsx(
|
|
420
|
+
Badge,
|
|
421
|
+
{
|
|
422
|
+
colorPalette: effectiveColorPalette,
|
|
423
|
+
variant: "subtle",
|
|
424
|
+
"data-testid": "sync-status",
|
|
425
|
+
"data-pending-count": pendingCount,
|
|
426
|
+
"data-processing": isProcessing,
|
|
427
|
+
...rest,
|
|
428
|
+
children: renderContent()
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export { FormOfflineIndicator, FormSyncStatus, addToQueue, clearQueue, createSyncQueueStore, getOfflineStatus, getQueueFromStorage, processQueueItem, removeFromQueue, subscribeToStatusChanges, useOfflineForm, useOfflineStatus, useSyncQueue };
|
|
434
|
+
//# sourceMappingURL=chunk-G3HYXHCZ.js.map
|
|
435
|
+
//# sourceMappingURL=chunk-G3HYXHCZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/offline/offline-service.ts","../src/lib/offline/use-offline-status.ts","../src/lib/offline/use-sync-queue.ts","../src/lib/offline/use-offline-form.ts","../src/lib/offline/form-offline-indicator.tsx","../src/lib/offline/form-sync-status.tsx"],"names":["listeners","notifyListeners","isOffline","useSyncExternalStore","useState","useCallback","useEffect","jsxs","HStack","jsx","Icon","Badge"],"mappings":"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","file":"chunk-G3HYXHCZ.js","sourcesContent":["/**\n * Сервис оффлайн-функциональности\n *\n * Бизнес-логика:\n * - Определение статуса онлайн/оффлайн\n * - Очередь синхронизации действий в IndexedDB\n */\n\nimport type { ProcessQueueResult, SyncAction, SyncActionHandler, SyncQueueItem, SyncQueueStore } from './types'\n\n// ============================================\n// ЛЕНИВЫЙ ИМПОРТ IDB-KEYVAL\n// ============================================\n\n/**\n * Проверка, что мы в браузере с поддержкой IndexedDB\n */\nfunction canUseIDB(): boolean {\n return typeof window !== 'undefined' && typeof indexedDB !== 'undefined'\n}\n\n/**\n * Ленивый импорт idb-keyval для избежания SSR проблем\n * Кэшируем промис для предотвращения повторных импортов\n */\nlet idbModule: typeof import('idb-keyval') | null = null\nasync function getIDB(): Promise<typeof import('idb-keyval') | null> {\n if (!canUseIDB()) {\n return null\n }\n if (!idbModule) {\n idbModule = await import('idb-keyval')\n }\n return idbModule\n}\n\n// ============================================\n// КОНСТАНТЫ\n// ============================================\n\nconst DEFAULT_SYNC_QUEUE_STORAGE_KEY = 'lena-form-sync-queue'\n\n// ============================================\n// СТАТУС СОЕДИНЕНИЯ\n// ============================================\n\n/**\n * Получить текущий статус оффлайн\n * @returns true если оффлайн, false если онлайн\n */\nexport function getOfflineStatus(): boolean {\n if (typeof navigator === 'undefined') {\n return false\n }\n return !navigator.onLine\n}\n\n/**\n * Подписаться на изменения статуса соединения\n * @param callback - функция, вызываемая при изменении статуса\n * @returns функция отписки\n */\nexport function subscribeToStatusChanges(callback: (isOffline: boolean) => void): () => void {\n if (typeof window === 'undefined') {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {}\n }\n\n const handleOnline = () => callback(false)\n const handleOffline = () => callback(true)\n\n window.addEventListener('online', handleOnline)\n window.addEventListener('offline', handleOffline)\n\n return () => {\n window.removeEventListener('online', handleOnline)\n window.removeEventListener('offline', handleOffline)\n }\n}\n\n// ============================================\n// ОЧЕРЕДЬ СИНХРОНИЗАЦИИ\n// ============================================\n\n/**\n * Генерация уникального ID\n */\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Получить очередь из IndexedDB\n */\nexport async function getQueueFromStorage(storageKey?: string): Promise<SyncQueueItem[]> {\n try {\n const idb = await getIDB()\n if (!idb) {\n return []\n }\n const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY\n const stored = await idb.get<SyncQueueItem[]>(key)\n return stored ?? []\n } catch (error) {\n console.error('[OfflineService] Ошибка загрузки очереди из IndexedDB:', error)\n return []\n }\n}\n\n/**\n * Сохранить очередь в IndexedDB\n */\nasync function saveQueueToStorage(queue: SyncQueueItem[], storageKey?: string): Promise<void> {\n try {\n const idb = await getIDB()\n if (!idb) {\n return\n }\n const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY\n await idb.set(key, queue)\n } catch (error) {\n console.error('[OfflineService] Ошибка сохранения очереди в IndexedDB:', error)\n }\n}\n\n/**\n * Добавить действие в очередь\n */\nexport async function addToQueue(action: SyncAction, storageKey?: string): Promise<SyncQueueItem> {\n const queue = await getQueueFromStorage(storageKey)\n\n const item: SyncQueueItem = {\n id: generateId(),\n action,\n createdAt: Date.now(),\n attempts: 0,\n maxAttempts: 3,\n status: 'PENDING',\n }\n\n queue.push(item)\n await saveQueueToStorage(queue, storageKey)\n\n return item\n}\n\n/**\n * Удалить элемент из очереди\n */\nexport async function removeFromQueue(id: string, storageKey?: string): Promise<boolean> {\n const queue = await getQueueFromStorage(storageKey)\n const index = queue.findIndex((item) => item.id === id)\n\n if (index === -1) {\n return false\n }\n\n queue.splice(index, 1)\n await saveQueueToStorage(queue, storageKey)\n\n return true\n}\n\n/**\n * Обработать один элемент очереди\n */\nexport async function processQueueItem(item: SyncQueueItem, handler: SyncActionHandler): Promise<ProcessQueueResult> {\n try {\n const result = await handler(item.action)\n\n if (result.success) {\n return {\n success: true,\n item: { ...item, status: 'SYNCED' as const },\n }\n }\n\n // Неуспешный результат без исключения\n const updatedItem: SyncQueueItem = {\n ...item,\n attempts: item.attempts + 1,\n status: item.attempts + 1 >= item.maxAttempts ? 'FAILED' : 'PENDING',\n error: result.error,\n }\n\n return {\n success: false,\n item: updatedItem,\n error: result.error,\n }\n } catch (error) {\n // Исключение при выполнении\n const errorMessage = error instanceof Error ? error.message : 'Unknown error'\n const updatedItem: SyncQueueItem = {\n ...item,\n attempts: item.attempts + 1,\n status: item.attempts + 1 >= item.maxAttempts ? 'FAILED' : 'PENDING',\n error: errorMessage,\n }\n\n return {\n success: false,\n item: updatedItem,\n error: errorMessage,\n }\n }\n}\n\n/**\n * Очистить очередь\n */\nexport async function clearQueue(storageKey?: string): Promise<void> {\n try {\n const idb = await getIDB()\n if (!idb) {\n return\n }\n const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY\n await idb.del(key)\n } catch (error) {\n console.error('[OfflineService] Ошибка очистки очереди из IndexedDB:', error)\n }\n}\n\n/**\n * Создать store для очереди синхронизации\n */\nexport function createSyncQueueStore(storageKey?: string): SyncQueueStore {\n let queue: SyncQueueItem[] = []\n const listeners = new Set<() => void>()\n const key = storageKey ?? DEFAULT_SYNC_QUEUE_STORAGE_KEY\n\n const notifyListeners = () => {\n listeners.forEach((listener) => listener())\n }\n\n return {\n getQueue: () => queue,\n\n getQueueLength: () => queue.length,\n\n subscribe: (listener: () => void) => {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n\n initialize: async () => {\n queue = await getQueueFromStorage(key)\n notifyListeners()\n },\n\n add: async (action: SyncAction) => {\n const item = await addToQueue(action, key)\n queue.push(item)\n notifyListeners()\n return item\n },\n\n remove: async (id: string) => {\n const result = await removeFromQueue(id, key)\n if (result) {\n queue = queue.filter((item) => item.id !== id)\n notifyListeners()\n }\n return result\n },\n\n processAll: async (handler: SyncActionHandler) => {\n const results: ProcessQueueResult[] = []\n\n for (const item of queue) {\n if (item.status === 'PENDING') {\n const result = await processQueueItem(item, handler)\n results.push(result)\n\n // Обновляем очередь после обработки\n if (result.success && result.item) {\n await removeFromQueue(item.id, key)\n queue = queue.filter((q) => q.id !== item.id)\n } else if (result.item) {\n // Обновляем элемент в очереди\n const index = queue.findIndex((q) => q.id === item.id)\n if (index !== -1) {\n queue[index] = result.item\n }\n await saveQueueToStorage(queue, key)\n }\n }\n }\n\n notifyListeners()\n return results\n },\n }\n}\n","'use client'\n\nimport { useSyncExternalStore } from 'react'\n\nimport { getOfflineStatus, subscribeToStatusChanges } from './offline-service'\n\n// Глобальное состояние для синхронизации между вкладками\nlet isOffline = false\n\nconst listeners = new Set<() => void>()\n\nconst notifyListeners = () => {\n listeners.forEach((listener) => listener())\n}\n\n// Инициализация при первой загрузке\nif (typeof window !== 'undefined') {\n isOffline = getOfflineStatus()\n\n subscribeToStatusChanges((offline) => {\n isOffline = offline\n notifyListeners()\n })\n}\n\n/**\n * Хук для определения статуса оффлайн\n *\n * @returns true если браузер оффлайн\n *\n * @example\n * ```tsx\n * import { useOfflineStatus } from '@lena/form-components/offline'\n *\n * function MyComponent() {\n * const isOffline = useOfflineStatus()\n *\n * if (isOffline) {\n * return <OfflineBanner />\n * }\n *\n * return <OnlineContent />\n * }\n * ```\n */\nexport function useOfflineStatus(): boolean {\n return useSyncExternalStore(\n (callback) => {\n listeners.add(callback)\n return () => {\n listeners.delete(callback)\n }\n },\n () => isOffline,\n () => false // SSR fallback — считаем что онлайн\n )\n}\n","'use client'\n\nimport { useCallback, useEffect, useState, useSyncExternalStore } from 'react'\n\nimport { createSyncQueueStore } from './offline-service'\nimport type { ProcessQueueResult, SyncAction, SyncActionHandler, SyncQueueItem, UseSyncQueueResult } from './types'\nimport { useOfflineStatus } from './use-offline-status'\n\n// Глобальный store для очереди синхронизации (по умолчанию)\nconst defaultSyncQueueStore = createSyncQueueStore()\nconst EMPTY_QUEUE: SyncQueueItem[] = []\n\n// Флаг инициализации\nlet initialized = false\n\nconst initialize = async () => {\n if (!initialized && typeof window !== 'undefined') {\n initialized = true\n await defaultSyncQueueStore.initialize()\n }\n}\n\n/**\n * Хук для работы с очередью синхронизации\n *\n * Позволяет добавлять действия в очередь при оффлайн режиме\n * и синхронизировать их при восстановлении соединения.\n *\n * @example\n * ```tsx\n * import { useSyncQueue } from '@lena/form-components/offline'\n *\n * function MyComponent() {\n * const { queue, queueLength, addAction, processQueue, isProcessing } = useSyncQueue()\n *\n * // Добавление действия в очередь (работает и оффлайн)\n * const handleBookLesson = async (slotId: string) => {\n * if (isOffline) {\n * await addAction({ type: 'BOOK_LESSON', payload: { slotId } })\n * toast({ title: 'Действие добавлено в очередь синхронизации' })\n * } else {\n * await api.bookLesson(slotId)\n * }\n * }\n *\n * // Обработка очереди при восстановлении соединения\n * useEffect(() => {\n * if (!isOffline && queueLength > 0) {\n * processQueue(async (action) => {\n * switch (action.type) {\n * case 'BOOK_LESSON':\n * return api.bookLesson(action.payload.slotId)\n * // ... другие типы действий\n * }\n * })\n * }\n * }, [isOffline, queueLength, processQueue])\n * }\n * ```\n */\nexport function useSyncQueue(): UseSyncQueueResult {\n const [isLoading, setIsLoading] = useState(true)\n const [isProcessing, setIsProcessing] = useState(false)\n const isOffline = useOfflineStatus()\n\n // Подписка на изменения очереди\n const queue = useSyncExternalStore(\n (callback) => defaultSyncQueueStore.subscribe(callback),\n () => defaultSyncQueueStore.getQueue(),\n () => EMPTY_QUEUE // SSR fallback\n )\n\n // Инициализация при монтировании\n useEffect(() => {\n initialize().then(() => {\n setIsLoading(false)\n })\n }, [])\n\n // Добавление действия в очередь\n const addAction = useCallback(async (action: SyncAction): Promise<SyncQueueItem> => {\n return defaultSyncQueueStore.add(action)\n }, [])\n\n // Удаление действия из очереди\n const removeAction = useCallback(async (id: string): Promise<boolean> => {\n return defaultSyncQueueStore.remove(id)\n }, [])\n\n // Обработка всей очереди\n const processQueue = useCallback(\n async (handler: SyncActionHandler): Promise<ProcessQueueResult[]> => {\n if (isOffline) {\n console.warn('[SyncQueue] Невозможно обработать очередь в оффлайн режиме')\n return []\n }\n\n setIsProcessing(true)\n try {\n return await defaultSyncQueueStore.processAll(handler)\n } finally {\n setIsProcessing(false)\n }\n },\n [isOffline]\n )\n\n return {\n queue,\n queueLength: queue.length,\n pendingCount: queue.filter((item) => item.status === 'PENDING').length,\n isLoading,\n isProcessing,\n addAction,\n removeAction,\n processQueue,\n }\n}\n","'use client'\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { OfflineSubmitResult, SyncAction, UseOfflineFormOptions, UseOfflineFormResult } from './types'\nimport { useOfflineStatus } from './use-offline-status'\nimport { useSyncQueue } from './use-sync-queue'\n\n/**\n * Хук для оффлайн-поддержки форм с TanStack Form\n *\n * Автоматически определяет статус соединения и:\n * - Онлайн: отправляет данные напрямую\n * - Оффлайн: сохраняет в очередь IndexedDB для синхронизации\n *\n * @example\n * ```tsx\n * import { useOfflineForm } from '@lena/form-components/offline'\n *\n * function ProfileForm({ initialData }) {\n * const { submit, isOffline, pendingCount, isProcessing } = useOfflineForm({\n * actionType: 'UPDATE_PROFILE',\n * onlineSubmit: async (value) => {\n * const result = await updateProfileAction(value)\n * return { success: result.success, error: result.error?.formErrors?.[0] }\n * },\n * onSuccess: () => toaster.success({ title: 'Сохранено' }),\n * onQueued: () => toaster.info({ title: 'Сохранено локально' }),\n * onError: (error) => toaster.error({ title: 'Ошибка', description: error }),\n * })\n *\n * const form = useAppForm({\n * defaultValues: initialData,\n * onSubmit: async ({ value }) => {\n * await submit(value)\n * },\n * })\n *\n * return (\n * <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit() }}>\n * {isOffline && <Badge colorPalette=\"orange\">Оффлайн режим</Badge>}\n * {pendingCount > 0 && (\n * <Badge colorPalette=\"blue\">\n * {isProcessing ? 'Синхронизация...' : `Ожидает: ${pendingCount}`}\n * </Badge>\n * )}\n * <form.AppField name=\"name\" children={(field) => <field.TextField label=\"Имя\" />} />\n * <Button type=\"submit\">{isOffline ? 'Сохранить локально' : 'Сохранить'}</Button>\n * </form>\n * )\n * }\n * ```\n */\nexport function useOfflineForm<T extends object>({\n actionType,\n onlineSubmit,\n onSuccess,\n onQueued,\n onError,\n}: UseOfflineFormOptions<T>): UseOfflineFormResult<T> {\n const isOffline = useOfflineStatus()\n const { addAction, processQueue, pendingCount, isProcessing, queueLength } = useSyncQueue()\n const [lastSyncAttempt, setLastSyncAttempt] = useState<number | null>(null)\n\n // Ref для предотвращения повторной обработки очереди\n const processingRef = useRef(false)\n\n /**\n * Отправка формы с поддержкой оффлайн\n */\n const submit = useCallback(\n async (value: T): Promise<OfflineSubmitResult> => {\n // Оффлайн — добавляем в очередь\n if (isOffline) {\n try {\n const queueItem = await addAction({\n type: actionType,\n payload: value as Record<string, unknown>,\n })\n\n onQueued?.()\n\n return {\n success: true,\n queued: true,\n queueItemId: queueItem.id,\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Ошибка сохранения в очередь'\n onError?.(errorMessage)\n return {\n success: false,\n error: errorMessage,\n }\n }\n }\n\n // Онлайн — отправляем напрямую\n try {\n const result = await onlineSubmit(value)\n\n if (result.success) {\n onSuccess?.()\n } else if (result.error) {\n onError?.(result.error)\n }\n\n return {\n success: result.success,\n error: result.error,\n queued: false,\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Ошибка отправки'\n onError?.(errorMessage)\n return {\n success: false,\n error: errorMessage,\n queued: false,\n }\n }\n },\n [isOffline, actionType, addAction, onlineSubmit, onSuccess, onQueued, onError]\n )\n\n /**\n * Обработчик действий из очереди\n * Вызывается при восстановлении соединения\n */\n const handleQueuedAction = useCallback(\n async (action: SyncAction): Promise<{ success: boolean; error?: string }> => {\n // Обрабатываем только наш тип действия\n if (action.type !== actionType) {\n return { success: true } // Пропускаем чужие действия\n }\n\n return onlineSubmit(action.payload as T)\n },\n [actionType, onlineSubmit]\n )\n\n // Автоматическая синхронизация при восстановлении соединения\n useEffect(() => {\n // Если онлайн и есть элементы в очереди — пробуем синхронизировать\n if (!isOffline && pendingCount > 0 && !processingRef.current) {\n processingRef.current = true\n setLastSyncAttempt(Date.now())\n\n processQueue(handleQueuedAction)\n .then((results) => {\n const failed = results.filter((r) => !r.success)\n if (failed.length > 0) {\n console.warn(`[OfflineForm] ${failed.length} действий не удалось синхронизировать`)\n }\n })\n .finally(() => {\n processingRef.current = false\n })\n }\n }, [isOffline, pendingCount, processQueue, handleQueuedAction])\n\n return {\n submit,\n isOffline,\n pendingCount,\n queueLength,\n isProcessing,\n lastSyncAttempt,\n }\n}\n","'use client'\n\nimport { Badge, type BadgeProps, HStack, Icon } from '@chakra-ui/react'\nimport { LuWifiOff } from 'react-icons/lu'\n\nimport type { OfflineIndicatorProps } from './types'\nimport { useOfflineStatus } from './use-offline-status'\n\n/**\n * Индикатор оффлайн режима\n *\n * Автоматически отображается когда браузер оффлайн.\n * Скрывается при восстановлении соединения.\n *\n * @example\n * ```tsx\n * import { Form } from '@lena/form-components'\n *\n * <Form initialValue={data} onSubmit={handleSubmit}>\n * <Form.OfflineIndicator />\n * <Form.Field.String name=\"title\" />\n * <Form.Button.Submit />\n * </Form>\n * ```\n *\n * @example С настройками\n * ```tsx\n * <Form.OfflineIndicator\n * label=\"Нет связи\"\n * colorPalette=\"red\"\n * variant=\"solid\"\n * />\n * ```\n */\nexport function FormOfflineIndicator({\n label = 'Оффлайн режим',\n colorPalette = 'orange',\n variant = 'subtle',\n ...rest\n}: OfflineIndicatorProps & Omit<BadgeProps, 'children'>) {\n const isOffline = useOfflineStatus()\n\n if (!isOffline) {\n return null\n }\n\n return (\n <Badge colorPalette={colorPalette} variant={variant} data-testid=\"offline-indicator\" {...rest}>\n <HStack gap={1}>\n <Icon asChild boxSize={3}>\n <LuWifiOff />\n </Icon>\n <span>{label}</span>\n </HStack>\n </Badge>\n )\n}\n","'use client'\n\nimport { Badge, type BadgeProps, HStack, Icon, Spinner } from '@chakra-ui/react'\nimport { LuCheck, LuClock } from 'react-icons/lu'\n\nimport type { SyncStatusProps } from './types'\nimport { useOfflineStatus } from './use-offline-status'\nimport { useSyncQueue } from './use-sync-queue'\n\n/**\n * Индикатор статуса синхронизации очереди\n *\n * Показывает:\n * - Количество ожидающих действий\n * - Spinner при синхронизации\n * - \"Синхронизировано\" когда очередь пуста\n *\n * Работает глобально, не требует Form контекста.\n *\n * @example\n * ```tsx\n * import { FormSyncStatus } from '@lena/form-components/offline'\n *\n * // В layout или header\n * <FormSyncStatus />\n * ```\n *\n * @example С настройками\n * ```tsx\n * <FormSyncStatus\n * showWhenEmpty={false}\n * syncingLabel=\"Синхронизация...\"\n * pendingLabel={(count) => `Ожидает: ${count}`}\n * syncedLabel=\"Всё синхронизировано\"\n * />\n * ```\n */\nexport function FormSyncStatus({\n showWhenEmpty = false,\n syncingLabel = 'Синхронизация...',\n pendingLabel = (count: number) => `Ожидает: ${count}`,\n syncedLabel = 'Синхронизировано',\n colorPalette = 'blue',\n ...rest\n}: SyncStatusProps & Omit<BadgeProps, 'children'>) {\n const isOffline = useOfflineStatus()\n const { pendingCount, isProcessing } = useSyncQueue()\n\n // Скрываем если онлайн, очередь пуста и showWhenEmpty = false\n if (!isOffline && pendingCount === 0 && !isProcessing && !showWhenEmpty) {\n return null\n }\n\n // Определяем что показывать\n const renderContent = () => {\n // Синхронизация в процессе\n if (isProcessing) {\n return (\n <HStack gap={1}>\n <Spinner size=\"xs\" />\n <span>{syncingLabel}</span>\n </HStack>\n )\n }\n\n // Есть ожидающие элементы\n if (pendingCount > 0) {\n const label = typeof pendingLabel === 'function' ? pendingLabel(pendingCount) : pendingLabel\n return (\n <HStack gap={1}>\n <Icon asChild boxSize={3}>\n <LuClock />\n </Icon>\n <span>{label}</span>\n </HStack>\n )\n }\n\n // Всё синхронизировано\n return (\n <HStack gap={1}>\n <Icon asChild boxSize={3}>\n <LuCheck />\n </Icon>\n <span>{syncedLabel}</span>\n </HStack>\n )\n }\n\n // Цвет зависит от состояния\n const effectiveColorPalette = pendingCount > 0 ? 'orange' : isProcessing ? colorPalette : 'green'\n\n return (\n <Badge\n colorPalette={effectiveColorPalette}\n variant=\"subtle\"\n data-testid=\"sync-status\"\n data-pending-count={pendingCount}\n data-processing={isProcessing}\n {...rest}\n >\n {renderContent()}\n </Badge>\n )\n}\n"]}
|