@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.
- package/README.md +72 -6
- package/package.json +1 -1
- package/src/dataImport/dataImport.js +83 -3
- package/src/dataImport/designs/DataImportSample.jsx +52 -6
- package/src/dataImport/designs/DroppedRowsSample.jsx +65 -0
- package/src/dataImport/designs/ImportHistorySample.jsx +26 -3
- package/src/dataImport/designs/dataImport.css +92 -0
- package/src/dataImport/designs/index.js +1 -0
- package/src/dataImport/droppedRows.js +86 -0
- package/src/dataImport/pages.jsx +26 -3
- package/src/dataImport/useDataImportController.js +84 -7
- package/src/dataImport/useDroppedRowsController.js +95 -0
- package/src/dataImport/validateDesign.js +7 -0
- package/src/fileUpload/uploadFile.js +10 -0
- package/src/imports/designs/ImportsSample.jsx +72 -0
- package/src/imports/designs/imports.css +29 -0
- package/src/imports/designs/index.js +1 -0
- package/src/imports/importsApi.js +96 -0
- package/src/imports/pages.jsx +24 -0
- package/src/imports/useImportsController.js +125 -0
- package/src/imports/validateDesign.js +6 -0
- package/src/index.js +21 -1
|
@@ -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,
|