@xeplr/ui-utils 1.0.10 → 1.0.12

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.
@@ -0,0 +1,125 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { createImportsApi, importProgressText, runResults, historyJob, databasesOf } from './importsApi.js';
3
+
4
+ /**
5
+ * The whole import screen against @xeplr/imports' routes: which database,
6
+ * the import itself (DataImportPage), its progress and ✕, and the history
7
+ * (ImportHistoryPage) with dropped rows, rollback and delete. No React-free
8
+ * logic lives here — see importsApi.js.
9
+ *
10
+ * @param {object} props
11
+ * @param {string} [props.basePath='/imports'] - where the app mounted the router, as the browser sees it
12
+ * @param {number} [props.pollMs=1000] - how often a running import is asked how far it has got
13
+ */
14
+ export function useImportsController(props = {}) {
15
+ var api = useMemo(function() { return createImportsApi(props.basePath); }, [props.basePath]);
16
+ var pollMs = props.pollMs || 1000;
17
+
18
+ var [targets, setTargets] = useState([]);
19
+ var [targetsError, setTargetsError] = useState('');
20
+ var [connectionId, setConnectionId] = useState('');
21
+ var [dbInfoId, setDbInfoId] = useState('');
22
+ // The import running now: { runId, startedAt, progress }.
23
+ var [importing, setImporting] = useState(null);
24
+ // Bumped to make the history list read itself again.
25
+ var [historyVersion, setHistoryVersion] = useState(0);
26
+ var mounted = useRef(true);
27
+ useEffect(function() { mounted.current = true; return function() { mounted.current = false; }; }, []);
28
+
29
+ useEffect(function() {
30
+ api.targets().then(function(list) {
31
+ if (!mounted.current) return;
32
+ setTargets(list);
33
+ if (list.length) {
34
+ setConnectionId(list[0].connectionId);
35
+ setDbInfoId(((list[0].databases || [])[0] || {}).id || '');
36
+ }
37
+ }, function(err) { if (mounted.current) setTargetsError(err.message); });
38
+ }, [api]);
39
+
40
+ function chooseConnection(id) {
41
+ setConnectionId(id);
42
+ setDbInfoId((databasesOf(targets, id)[0] || {}).id || '');
43
+ }
44
+
45
+ var onUpload = useCallback(function(file, onProgress) { return api.upload(file, onProgress); }, [api]);
46
+
47
+ var onInspect = useCallback(async function(filePath) {
48
+ var found = await api.inspect(filePath);
49
+ return { format: found.format, parts: found.sheets, checks: found.checks };
50
+ }, [api]);
51
+
52
+ // Bound to the chosen database, so choosing another asks again.
53
+ var onCheckTarget = useCallback(function(filePath, part) {
54
+ if (!connectionId || !dbInfoId) return Promise.resolve(null);
55
+ return api.checkTarget({ filePath: filePath, sheet: part.part, connectionId: connectionId, dbInfoId: dbInfoId, targetTable: part.target });
56
+ }, [api, connectionId, dbInfoId]);
57
+
58
+ // STARTS the import, then asks how far it has got until it ends. The
59
+ // request answers at once with a run id, so no gateway timeout applies.
60
+ var onCommit = useCallback(async function(filePath, mappings, meta) {
61
+ if (!connectionId || !dbInfoId) throw new Error('Choose a database to import into first');
62
+ var started = await api.commit({
63
+ filePath: filePath, connectionId: connectionId, dbInfoId: dbInfoId, originalFileName: meta && meta.originalName,
64
+ mappings: mappings.map(function(m) {
65
+ return { sheet: m.part, targetTable: m.target, mode: m.mode, repairPastedRows: Boolean(m.fixes && m.fixes.pastedRows) };
66
+ })
67
+ });
68
+ setImporting({ runId: started.runId, startedAt: Date.now(), progress: null });
69
+ setHistoryVersion(function(v) { return v + 1; }); // in the history from its first row
70
+ try {
71
+ for (;;) {
72
+ await new Promise(function(r) { setTimeout(r, pollMs); });
73
+ var run = await api.run(started.runId);
74
+ if (run.status !== 'running') return { results: runResults(run) };
75
+ if (mounted.current) setImporting(function(cur) { return cur && cur.runId === run.id ? Object.assign({}, cur, { progress: run.progress }) : cur; });
76
+ }
77
+ } finally {
78
+ if (mounted.current) {
79
+ setImporting(null);
80
+ setHistoryVersion(function(v) { return v + 1; });
81
+ }
82
+ }
83
+ }, [api, connectionId, dbInfoId, pollMs]);
84
+
85
+ function stop() {
86
+ if (importing) api.stop(importing.runId).catch(function() {});
87
+ }
88
+
89
+ var onList = useCallback(async function() {
90
+ return { jobs: (await api.history()).map(historyJob) };
91
+ }, [api]);
92
+ var onRollback = useCallback(function(job) { return api.rollback(job.id); }, [api]);
93
+ var onDelete = useCallback(function(job) { return api.remove(job.id); }, [api]);
94
+ var onLoadDropped = useCallback(function(job) { return api.dropped(job.id); }, [api]);
95
+ var onDownloadDropped = useCallback(async function(job) {
96
+ var file = await api.droppedFile(job.id);
97
+ var url = URL.createObjectURL(file.blob);
98
+ var a = document.createElement('a');
99
+ a.href = url;
100
+ a.download = file.name;
101
+ document.body.appendChild(a);
102
+ a.click();
103
+ a.remove();
104
+ setTimeout(function() { URL.revokeObjectURL(url); }, 0);
105
+ }, [api]);
106
+
107
+ return {
108
+ targets: targets,
109
+ targetsError: targetsError,
110
+ connectionId: connectionId,
111
+ dbInfoId: dbInfoId,
112
+ databases: databasesOf(targets, connectionId),
113
+ chooseConnection: chooseConnection,
114
+ chooseDatabase: setDbInfoId,
115
+ importing: importing,
116
+ progressText: importing ? importProgressText(importing.progress) : '',
117
+ stop: stop,
118
+ historyVersion: historyVersion,
119
+ // What the two built-in pages take.
120
+ importProps: { onUpload: onUpload, onInspect: onInspect, onCheckTarget: onCheckTarget, onCommit: onCommit,
121
+ onLoadDropped: onLoadDropped, onDownloadDropped: onDownloadDropped },
122
+ historyProps: { onList: onList, onRollback: onRollback, onDelete: onDelete,
123
+ onLoadDropped: onLoadDropped, onDownloadDropped: onDownloadDropped }
124
+ };
125
+ }
@@ -0,0 +1,6 @@
1
+ // What an ImportsPage design must have: the two pickers that say where the
2
+ // file goes. (The import and history sections validate themselves.)
3
+ export var IMPORTS_RULES = [
4
+ { id: 'xeplr-imports-connection', label: 'Connection picker' },
5
+ { id: 'xeplr-imports-database', label: 'Database picker' }
6
+ ];
package/src/index.js CHANGED
@@ -17,7 +17,7 @@ export { FileUploadPage } from './fileUpload/pages.jsx';
17
17
  // the whole file, for CSV/JSON) to app-defined named targets, commit. The
18
18
  // app supplies onInspect/onCommit (its own upload + load endpoints); this
19
19
  // module only owns the file-pick -> mapping -> commit flow and UI.
20
- export { sanitizeTargetName, buildInitialMappings } from './dataImport/dataImport.js';
20
+ export { sanitizeTargetName, buildInitialMappings, IMPORT_MODES, DEFAULT_MODE, targetProblems, blocksImport, canRollback, modeLabel } from './dataImport/dataImport.js';
21
21
 
22
22
  // DataImport — controller
23
23
  export { useDataImportController } from './dataImport/useDataImportController.js';
@@ -40,6 +40,26 @@ export { IMPORT_HISTORY_RULES } from './dataImport/validateDesign.js';
40
40
  export { ImportHistorySample } from './dataImport/designs/index.js';
41
41
  export { ImportHistoryPage } from './dataImport/pages.jsx';
42
42
 
43
+ // DroppedRows — the rows an import's database refused, with each one's place
44
+ // in the file and the reason; Copy (tab-separated, pastes into Excel) and
45
+ // Download (app-supplied onDownloadDropped). Built into DataImportPage and
46
+ // ImportHistoryPage when the app passes onLoadDropped.
47
+ export { droppedCount, droppedTable, droppedAsTsv, droppedSummary, tsvCell } from './dataImport/droppedRows.js';
48
+ export { useDroppedRowsController } from './dataImport/useDroppedRowsController.js';
49
+ export { DROPPED_ROWS_RULES } from './dataImport/validateDesign.js';
50
+ export { DroppedRowsSample } from './dataImport/designs/index.js';
51
+ export { DroppedRowsPage } from './dataImport/pages.jsx';
52
+
53
+ // Imports — the whole import screen against @xeplr/imports' routes: database
54
+ // picker, import (DataImportPage), progress with ✕, history (ImportHistoryPage).
55
+ // <ImportsPage basePath="/api/imports" /> is all an app adds.
56
+ export { createImportsApi, importProgressText, runResults, historyJob, databasesOf, fileNameOf } from './imports/importsApi.js';
57
+ export { useImportsController } from './imports/useImportsController.js';
58
+ export { IMPORTS_RULES } from './imports/validateDesign.js';
59
+ export { ImportsSample } from './imports/designs/index.js';
60
+ export { ImportsPage } from './imports/pages.jsx';
61
+ export { uploadHeaders } from './fileUpload/uploadFile.js';
62
+
43
63
  // NumberField — model
44
64
  export {
45
65
  NUMBER_TYPES,