@elice/material-exercise 1.221101.0 → 1.221109.1
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.
|
@@ -54,14 +54,20 @@ const FileViewer = ({
|
|
|
54
54
|
*/
|
|
55
55
|
|
|
56
56
|
const PreviewComponent = React__default["default"].useMemo(() => {
|
|
57
|
-
//
|
|
58
|
-
if (
|
|
59
|
-
return
|
|
60
|
-
} // Text file
|
|
57
|
+
// Binary file which shows as text
|
|
58
|
+
if (showInTextViewer) {
|
|
59
|
+
return AsyncFileViewerText;
|
|
60
|
+
} // Text file
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
if (
|
|
64
|
-
|
|
63
|
+
if (mimeType.startsWith('text/')) {
|
|
64
|
+
// csv
|
|
65
|
+
if (mimeType === 'text/csv') {
|
|
66
|
+
return AsyncFileViewerCsv;
|
|
67
|
+
} // other text file
|
|
68
|
+
else {
|
|
69
|
+
return AsyncFileViewerText;
|
|
70
|
+
}
|
|
65
71
|
} // Image file
|
|
66
72
|
|
|
67
73
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
|
-
var
|
|
4
|
+
var reactIntl = require('react-intl');
|
|
5
|
+
var blocks = require('@elice/blocks');
|
|
6
|
+
var designTokens = require('@elice/design-tokens');
|
|
7
|
+
var icons = require('@elice/icons');
|
|
5
8
|
var styled = require('styled-components');
|
|
6
9
|
var ExerciseFileShimmer = require('../exercise-shimmer/ExerciseFileShimmer.js');
|
|
7
10
|
require('../exercise-shimmer/ExerciseFileTabsShimmer.js');
|
|
@@ -14,33 +17,289 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
14
17
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
15
18
|
var styled__default = /*#__PURE__*/_interopDefaultLegacy(styled);
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
//
|
|
21
|
+
//
|
|
22
|
+
|
|
23
|
+
const OPTIONS_PER_PAGE = [10, 25, 50];
|
|
24
|
+
const OPTIONS_DELIMITER = [{
|
|
25
|
+
char: ',',
|
|
26
|
+
label: 'Comma [,]'
|
|
27
|
+
}, {
|
|
28
|
+
char: '\t',
|
|
29
|
+
label: 'Tab [\\t]'
|
|
30
|
+
}, {
|
|
31
|
+
char: '/',
|
|
32
|
+
label: 'Regular Bar [/]'
|
|
33
|
+
}, {
|
|
34
|
+
char: '\\',
|
|
35
|
+
label: 'Inverted Bar [\\]'
|
|
36
|
+
}, {
|
|
37
|
+
char: '|',
|
|
38
|
+
label: 'Vertical Bar [|]'
|
|
39
|
+
}]; //
|
|
40
|
+
//
|
|
41
|
+
//
|
|
42
|
+
|
|
43
|
+
const StyledViewer = styled__default["default"].div.withConfig({
|
|
18
44
|
componentId: "sc-pyv7uu-0"
|
|
19
|
-
})(["flex:1
|
|
45
|
+
})(["flex:1;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background-color:", ";"], designTokens.base.color.navy9);
|
|
46
|
+
const StyledViewerTableWrap = styled__default["default"].div.withConfig({
|
|
47
|
+
componentId: "sc-pyv7uu-1"
|
|
48
|
+
})(["flex:1;overflow:auto;"]);
|
|
49
|
+
const StyledViewerTable = styled__default["default"].table.withConfig({
|
|
50
|
+
componentId: "sc-pyv7uu-2"
|
|
51
|
+
})(["border:none;border-collapse:collapse;color:", ";font-family:'Elice Digital Coding',monospace,fixed-width;th,td{border:1px solid ", ";padding:0.5rem;white-space:pre;&:first-of-type{color:", ";border-left:none;}&:last-of-type{border-right:none;}}tbody{tr:nth-child(odd){background:", ";}tr:hover{background:", ";}}"], designTokens.base.color.white, designTokens.base.color.navy5, designTokens.base.color.gray5, designTokens.base.color.navy8, designTokens.base.color.navy6);
|
|
52
|
+
const StyledViewerFooter = styled__default["default"].div.withConfig({
|
|
53
|
+
componentId: "sc-pyv7uu-3"
|
|
54
|
+
})(["flex:0 0 auto;display:flex;justify-content:space-between;align-items:center;padding:0.5rem;"]);
|
|
55
|
+
const StyledViewerFooterSelect = styled__default["default"].select.withConfig({
|
|
56
|
+
componentId: "sc-pyv7uu-4"
|
|
57
|
+
})(["width:", "rem;border-radius:0.25rem;"], props => props.width);
|
|
58
|
+
const StyledViewerFooterHr = styled__default["default"](blocks.Hr).withConfig({
|
|
59
|
+
componentId: "sc-pyv7uu-5"
|
|
60
|
+
})(["height:1rem;"]); //
|
|
61
|
+
//
|
|
62
|
+
//
|
|
20
63
|
|
|
21
64
|
const FileViewerCsv = ({
|
|
22
65
|
fileurl
|
|
23
66
|
}) => {
|
|
24
|
-
const [
|
|
67
|
+
const [csv, setCsv] = React__default["default"].useState('');
|
|
68
|
+
const [csvHeader, setCsvHeader] = React__default["default"].useState(null);
|
|
69
|
+
const [csvRecords, setCsvRecords] = React__default["default"].useState([]);
|
|
70
|
+
const [csvStatus, setCsvStatus] = React__default["default"].useState('idle');
|
|
71
|
+
const [page, setPage] = React__default["default"].useState(1);
|
|
72
|
+
const [perPage, setPerPage] = React__default["default"].useState(OPTIONS_PER_PAGE[0]);
|
|
73
|
+
const [delimiter, setDelimiter] = React__default["default"].useState(OPTIONS_DELIMITER[0].char);
|
|
74
|
+
const [isUsingHeader, setUsingHeader] = React__default["default"].useState(true);
|
|
75
|
+
const csvRecordCount = csvRecords.length + (csvHeader ? -1 : 0);
|
|
76
|
+
const maxPage = Math.ceil(csvRecordCount / perPage);
|
|
77
|
+
const tableWrapElRef = React__default["default"].useRef(null); //
|
|
78
|
+
// Fetch csv file
|
|
79
|
+
//
|
|
80
|
+
|
|
25
81
|
React__default["default"].useEffect(() => {
|
|
26
82
|
if (!fileurl) {
|
|
83
|
+
setCsvRecords([]);
|
|
27
84
|
return;
|
|
28
85
|
}
|
|
29
86
|
|
|
30
|
-
|
|
87
|
+
const abortCtrl = new AbortController();
|
|
88
|
+
setCsvStatus('pending');
|
|
89
|
+
fetch(fileurl, {
|
|
90
|
+
signal: abortCtrl.signal
|
|
91
|
+
}).then(res => res.text()).then(setCsv).then(() => setCsvStatus('resolved')).catch(err => {
|
|
92
|
+
if (err.name === 'AbortError') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setCsvRecords([]);
|
|
97
|
+
setCsvStatus('rejected');
|
|
98
|
+
});
|
|
99
|
+
return () => {
|
|
100
|
+
abortCtrl.abort();
|
|
101
|
+
};
|
|
31
102
|
}, [fileurl]); //
|
|
103
|
+
// Parse csv file and save as records
|
|
104
|
+
//
|
|
105
|
+
|
|
106
|
+
React__default["default"].useEffect(() => setCsvRecords(csv.split('\n').filter(Boolean).map(row => row.split(delimiter))), [csv, delimiter]); //
|
|
107
|
+
// Set CSV header.
|
|
32
108
|
//
|
|
109
|
+
|
|
110
|
+
React__default["default"].useEffect(() => setCsvHeader(isUsingHeader ? csvRecords[0] : null), [csvRecords, isUsingHeader]); //
|
|
111
|
+
// Scroll to top when page changes.
|
|
33
112
|
//
|
|
34
113
|
|
|
35
|
-
|
|
114
|
+
React__default["default"].useEffect(() => {
|
|
115
|
+
var _a;
|
|
116
|
+
|
|
117
|
+
return (_a = tableWrapElRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo(0, 0);
|
|
118
|
+
}, [page]);
|
|
119
|
+
/**
|
|
120
|
+
*
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
const renderViewerTableThead = () => {
|
|
124
|
+
if (!csvHeader) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const colunms = ['', ...csvHeader];
|
|
129
|
+
return React__default["default"].createElement("thead", null, React__default["default"].createElement("tr", null, colunms.map((col, colIdx) => React__default["default"].createElement("th", {
|
|
130
|
+
key: colIdx
|
|
131
|
+
}, col))));
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
*
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
const renderViewerTableTbody = () => {
|
|
139
|
+
const start = (page - 1) * perPage;
|
|
140
|
+
const end = page * perPage;
|
|
141
|
+
const records = csvRecords.slice(csvHeader ? 1 : 0).slice(start, end);
|
|
142
|
+
return React__default["default"].createElement("tbody", null, records.map((row, rowIdx) => {
|
|
143
|
+
const curRecordNo = start + rowIdx + 1;
|
|
144
|
+
const curRow = [curRecordNo, ...row];
|
|
145
|
+
return React__default["default"].createElement("tr", {
|
|
146
|
+
key: curRecordNo
|
|
147
|
+
}, curRow.map((col, colIdx) => React__default["default"].createElement("td", {
|
|
148
|
+
key: colIdx,
|
|
149
|
+
style: {
|
|
150
|
+
// test whether the column is number, and align right
|
|
151
|
+
textAlign: // eslint-disable-next-line no-self-compare
|
|
152
|
+
+col === +col ? 'right' : 'left'
|
|
153
|
+
}
|
|
154
|
+
}, col)));
|
|
155
|
+
}));
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
*
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
const renderViewerTable = () => {
|
|
163
|
+
return React__default["default"].createElement(StyledViewerTableWrap, {
|
|
164
|
+
ref: tableWrapElRef
|
|
165
|
+
}, React__default["default"].createElement(StyledViewerTable, null, renderViewerTableThead(), renderViewerTableTbody()));
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
*
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
const renderViewerFooterPagination = () => {
|
|
173
|
+
return React__default["default"].createElement(blocks.Flex, {
|
|
174
|
+
align: "center"
|
|
175
|
+
}, React__default["default"].createElement(blocks.Text, {
|
|
176
|
+
role: "gray1",
|
|
177
|
+
size: "small"
|
|
178
|
+
}, csvRecordCount, " records"), React__default["default"].createElement(StyledViewerFooterHr, {
|
|
179
|
+
vertical: true,
|
|
180
|
+
color: designTokens.base.color.navy5
|
|
181
|
+
}), React__default["default"].createElement(blocks.Flex, {
|
|
182
|
+
align: "center"
|
|
183
|
+
}, React__default["default"].createElement(blocks.IconButton, {
|
|
184
|
+
icon: icons.eilArrowLeftwardsDouble,
|
|
185
|
+
role: "navy3",
|
|
186
|
+
size: "nano",
|
|
187
|
+
disabled: page <= 1,
|
|
188
|
+
onClick: () => setPage(1)
|
|
189
|
+
}), React__default["default"].createElement(blocks.Hspace, {
|
|
190
|
+
width: 0.25
|
|
191
|
+
}), React__default["default"].createElement(blocks.IconButton, {
|
|
192
|
+
icon: icons.eilArrowLeftwardsSingle,
|
|
193
|
+
role: "navy3",
|
|
194
|
+
size: "nano",
|
|
195
|
+
disabled: page <= 1,
|
|
196
|
+
onClick: () => setPage(prevPage => prevPage - 1)
|
|
197
|
+
}), React__default["default"].createElement(blocks.Hspace, {
|
|
198
|
+
width: 0.5
|
|
199
|
+
}), React__default["default"].createElement(blocks.Text, {
|
|
200
|
+
role: "gray1",
|
|
201
|
+
size: "small"
|
|
202
|
+
}, page, " / ", maxPage), React__default["default"].createElement(blocks.Hspace, {
|
|
203
|
+
width: 0.5
|
|
204
|
+
}), React__default["default"].createElement(blocks.IconButton, {
|
|
205
|
+
icon: icons.eilArrowRightwardsSingle,
|
|
206
|
+
role: "navy3",
|
|
207
|
+
size: "nano",
|
|
208
|
+
disabled: page >= maxPage,
|
|
209
|
+
onClick: () => setPage(prevPage => prevPage + 1)
|
|
210
|
+
}), React__default["default"].createElement(blocks.Hspace, {
|
|
211
|
+
width: 0.25
|
|
212
|
+
}), React__default["default"].createElement(blocks.IconButton, {
|
|
213
|
+
icon: icons.eilArrowRightwardsDouble,
|
|
214
|
+
role: "navy3",
|
|
215
|
+
size: "nano",
|
|
216
|
+
disabled: page >= maxPage,
|
|
217
|
+
onClick: () => setPage(maxPage)
|
|
218
|
+
})));
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
*
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
const renderViewerFooterSettings = () => {
|
|
226
|
+
return React__default["default"].createElement(blocks.Flex, {
|
|
227
|
+
align: "center"
|
|
228
|
+
}, React__default["default"].createElement(blocks.Flex, {
|
|
229
|
+
align: "center"
|
|
230
|
+
}, React__default["default"].createElement(blocks.Text, {
|
|
231
|
+
role: "gray1",
|
|
232
|
+
size: "small"
|
|
233
|
+
}, React__default["default"].createElement(reactIntl.FormattedMessage, {
|
|
234
|
+
id: "\uD5E4\uB354 \uC0AC\uC6A9"
|
|
235
|
+
})), React__default["default"].createElement(blocks.Hspace, {
|
|
236
|
+
width: 0.5
|
|
237
|
+
}), React__default["default"].createElement(blocks.AntSwitch, {
|
|
238
|
+
size: "small",
|
|
239
|
+
checked: isUsingHeader,
|
|
240
|
+
onChange: setUsingHeader
|
|
241
|
+
})), React__default["default"].createElement(StyledViewerFooterHr, {
|
|
242
|
+
vertical: true,
|
|
243
|
+
color: designTokens.base.color.navy5
|
|
244
|
+
}), React__default["default"].createElement(blocks.Flex, {
|
|
245
|
+
align: "center"
|
|
246
|
+
}, React__default["default"].createElement(blocks.Text, {
|
|
247
|
+
role: "gray1",
|
|
248
|
+
size: "small"
|
|
249
|
+
}, React__default["default"].createElement(reactIntl.FormattedMessage, {
|
|
250
|
+
id: "\uAD6C\uBD84\uC790:"
|
|
251
|
+
})), React__default["default"].createElement(blocks.Hspace, {
|
|
252
|
+
width: 0.5
|
|
253
|
+
}), React__default["default"].createElement(StyledViewerFooterSelect, {
|
|
254
|
+
width: 7.5,
|
|
255
|
+
value: delimiter,
|
|
256
|
+
onChange: e => setDelimiter(e.target.value)
|
|
257
|
+
}, OPTIONS_DELIMITER.map(({
|
|
258
|
+
char,
|
|
259
|
+
label
|
|
260
|
+
}) => React__default["default"].createElement("option", {
|
|
261
|
+
value: char
|
|
262
|
+
}, label)))), React__default["default"].createElement(StyledViewerFooterHr, {
|
|
263
|
+
vertical: true,
|
|
264
|
+
color: designTokens.base.color.navy5
|
|
265
|
+
}), React__default["default"].createElement(blocks.Flex, {
|
|
266
|
+
align: "center"
|
|
267
|
+
}, React__default["default"].createElement(blocks.Text, {
|
|
268
|
+
role: "gray1",
|
|
269
|
+
size: "small"
|
|
270
|
+
}, React__default["default"].createElement(reactIntl.FormattedMessage, {
|
|
271
|
+
id: "\uD398\uC774\uC9C0\uB2F9 \uD589:"
|
|
272
|
+
})), React__default["default"].createElement(blocks.Hspace, {
|
|
273
|
+
width: 0.5
|
|
274
|
+
}), React__default["default"].createElement(StyledViewerFooterSelect, {
|
|
275
|
+
width: 3,
|
|
276
|
+
value: perPage,
|
|
277
|
+
onChange: e => setPerPage(Number(e.target.value))
|
|
278
|
+
}, OPTIONS_PER_PAGE.map(perPage => React__default["default"].createElement("option", {
|
|
279
|
+
value: perPage
|
|
280
|
+
}, perPage)))));
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
*
|
|
284
|
+
*/
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
const renderViewerFooter = () => {
|
|
288
|
+
return React__default["default"].createElement(StyledViewerFooter, null, renderViewerFooterPagination(), renderViewerFooterSettings());
|
|
289
|
+
}; //
|
|
290
|
+
//
|
|
291
|
+
//
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if (csvStatus === 'idle' || csvStatus === 'pending') {
|
|
36
295
|
return React__default["default"].createElement(ExerciseFileShimmer, null);
|
|
37
296
|
}
|
|
38
297
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
298
|
+
if (csvStatus === 'rejected') {
|
|
299
|
+
return React__default["default"].createElement("div", null, "error");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return React__default["default"].createElement(StyledViewer, null, renderViewerTable(), renderViewerFooter());
|
|
44
303
|
};
|
|
45
304
|
|
|
46
305
|
module.exports = FileViewerCsv;
|
|
@@ -43,14 +43,20 @@ const FileViewer = ({
|
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
const PreviewComponent = React.useMemo(() => {
|
|
46
|
-
//
|
|
47
|
-
if (
|
|
48
|
-
return
|
|
49
|
-
} // Text file
|
|
46
|
+
// Binary file which shows as text
|
|
47
|
+
if (showInTextViewer) {
|
|
48
|
+
return AsyncFileViewerText;
|
|
49
|
+
} // Text file
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
52
|
+
if (mimeType.startsWith('text/')) {
|
|
53
|
+
// csv
|
|
54
|
+
if (mimeType === 'text/csv') {
|
|
55
|
+
return AsyncFileViewerCsv;
|
|
56
|
+
} // other text file
|
|
57
|
+
else {
|
|
58
|
+
return AsyncFileViewerText;
|
|
59
|
+
}
|
|
54
60
|
} // Image file
|
|
55
61
|
|
|
56
62
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { FormattedMessage } from 'react-intl';
|
|
3
|
+
import { Hr, Flex, Text, IconButton, Hspace, AntSwitch } from '@elice/blocks';
|
|
4
|
+
import { base } from '@elice/design-tokens';
|
|
5
|
+
import { eilArrowLeftwardsDouble, eilArrowLeftwardsSingle, eilArrowRightwardsSingle, eilArrowRightwardsDouble } from '@elice/icons';
|
|
3
6
|
import styled from 'styled-components';
|
|
4
7
|
import ExerciseFileShimmer from '../exercise-shimmer/ExerciseFileShimmer.js';
|
|
5
8
|
import '../exercise-shimmer/ExerciseFileTabsShimmer.js';
|
|
@@ -7,33 +10,289 @@ import '../exercise-shimmer/ExerciseFileTabShimmer.js';
|
|
|
7
10
|
import '../exercise-shimmer/ExerciseFileTreeListShimmer.js';
|
|
8
11
|
import '../exercise-shimmer/ExerciseFileTreeListItemShimmer.js';
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
//
|
|
14
|
+
//
|
|
15
|
+
|
|
16
|
+
const OPTIONS_PER_PAGE = [10, 25, 50];
|
|
17
|
+
const OPTIONS_DELIMITER = [{
|
|
18
|
+
char: ',',
|
|
19
|
+
label: 'Comma [,]'
|
|
20
|
+
}, {
|
|
21
|
+
char: '\t',
|
|
22
|
+
label: 'Tab [\\t]'
|
|
23
|
+
}, {
|
|
24
|
+
char: '/',
|
|
25
|
+
label: 'Regular Bar [/]'
|
|
26
|
+
}, {
|
|
27
|
+
char: '\\',
|
|
28
|
+
label: 'Inverted Bar [\\]'
|
|
29
|
+
}, {
|
|
30
|
+
char: '|',
|
|
31
|
+
label: 'Vertical Bar [|]'
|
|
32
|
+
}]; //
|
|
33
|
+
//
|
|
34
|
+
//
|
|
35
|
+
|
|
36
|
+
const StyledViewer = styled.div.withConfig({
|
|
11
37
|
componentId: "sc-pyv7uu-0"
|
|
12
|
-
})(["flex:1
|
|
38
|
+
})(["flex:1;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background-color:", ";"], base.color.navy9);
|
|
39
|
+
const StyledViewerTableWrap = styled.div.withConfig({
|
|
40
|
+
componentId: "sc-pyv7uu-1"
|
|
41
|
+
})(["flex:1;overflow:auto;"]);
|
|
42
|
+
const StyledViewerTable = styled.table.withConfig({
|
|
43
|
+
componentId: "sc-pyv7uu-2"
|
|
44
|
+
})(["border:none;border-collapse:collapse;color:", ";font-family:'Elice Digital Coding',monospace,fixed-width;th,td{border:1px solid ", ";padding:0.5rem;white-space:pre;&:first-of-type{color:", ";border-left:none;}&:last-of-type{border-right:none;}}tbody{tr:nth-child(odd){background:", ";}tr:hover{background:", ";}}"], base.color.white, base.color.navy5, base.color.gray5, base.color.navy8, base.color.navy6);
|
|
45
|
+
const StyledViewerFooter = styled.div.withConfig({
|
|
46
|
+
componentId: "sc-pyv7uu-3"
|
|
47
|
+
})(["flex:0 0 auto;display:flex;justify-content:space-between;align-items:center;padding:0.5rem;"]);
|
|
48
|
+
const StyledViewerFooterSelect = styled.select.withConfig({
|
|
49
|
+
componentId: "sc-pyv7uu-4"
|
|
50
|
+
})(["width:", "rem;border-radius:0.25rem;"], props => props.width);
|
|
51
|
+
const StyledViewerFooterHr = styled(Hr).withConfig({
|
|
52
|
+
componentId: "sc-pyv7uu-5"
|
|
53
|
+
})(["height:1rem;"]); //
|
|
54
|
+
//
|
|
55
|
+
//
|
|
13
56
|
|
|
14
57
|
const FileViewerCsv = ({
|
|
15
58
|
fileurl
|
|
16
59
|
}) => {
|
|
17
|
-
const [
|
|
60
|
+
const [csv, setCsv] = React.useState('');
|
|
61
|
+
const [csvHeader, setCsvHeader] = React.useState(null);
|
|
62
|
+
const [csvRecords, setCsvRecords] = React.useState([]);
|
|
63
|
+
const [csvStatus, setCsvStatus] = React.useState('idle');
|
|
64
|
+
const [page, setPage] = React.useState(1);
|
|
65
|
+
const [perPage, setPerPage] = React.useState(OPTIONS_PER_PAGE[0]);
|
|
66
|
+
const [delimiter, setDelimiter] = React.useState(OPTIONS_DELIMITER[0].char);
|
|
67
|
+
const [isUsingHeader, setUsingHeader] = React.useState(true);
|
|
68
|
+
const csvRecordCount = csvRecords.length + (csvHeader ? -1 : 0);
|
|
69
|
+
const maxPage = Math.ceil(csvRecordCount / perPage);
|
|
70
|
+
const tableWrapElRef = React.useRef(null); //
|
|
71
|
+
// Fetch csv file
|
|
72
|
+
//
|
|
73
|
+
|
|
18
74
|
React.useEffect(() => {
|
|
19
75
|
if (!fileurl) {
|
|
76
|
+
setCsvRecords([]);
|
|
20
77
|
return;
|
|
21
78
|
}
|
|
22
79
|
|
|
23
|
-
|
|
80
|
+
const abortCtrl = new AbortController();
|
|
81
|
+
setCsvStatus('pending');
|
|
82
|
+
fetch(fileurl, {
|
|
83
|
+
signal: abortCtrl.signal
|
|
84
|
+
}).then(res => res.text()).then(setCsv).then(() => setCsvStatus('resolved')).catch(err => {
|
|
85
|
+
if (err.name === 'AbortError') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setCsvRecords([]);
|
|
90
|
+
setCsvStatus('rejected');
|
|
91
|
+
});
|
|
92
|
+
return () => {
|
|
93
|
+
abortCtrl.abort();
|
|
94
|
+
};
|
|
24
95
|
}, [fileurl]); //
|
|
96
|
+
// Parse csv file and save as records
|
|
97
|
+
//
|
|
98
|
+
|
|
99
|
+
React.useEffect(() => setCsvRecords(csv.split('\n').filter(Boolean).map(row => row.split(delimiter))), [csv, delimiter]); //
|
|
100
|
+
// Set CSV header.
|
|
25
101
|
//
|
|
102
|
+
|
|
103
|
+
React.useEffect(() => setCsvHeader(isUsingHeader ? csvRecords[0] : null), [csvRecords, isUsingHeader]); //
|
|
104
|
+
// Scroll to top when page changes.
|
|
26
105
|
//
|
|
27
106
|
|
|
28
|
-
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
var _a;
|
|
109
|
+
|
|
110
|
+
return (_a = tableWrapElRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo(0, 0);
|
|
111
|
+
}, [page]);
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
const renderViewerTableThead = () => {
|
|
117
|
+
if (!csvHeader) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const colunms = ['', ...csvHeader];
|
|
122
|
+
return React.createElement("thead", null, React.createElement("tr", null, colunms.map((col, colIdx) => React.createElement("th", {
|
|
123
|
+
key: colIdx
|
|
124
|
+
}, col))));
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
*
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
const renderViewerTableTbody = () => {
|
|
132
|
+
const start = (page - 1) * perPage;
|
|
133
|
+
const end = page * perPage;
|
|
134
|
+
const records = csvRecords.slice(csvHeader ? 1 : 0).slice(start, end);
|
|
135
|
+
return React.createElement("tbody", null, records.map((row, rowIdx) => {
|
|
136
|
+
const curRecordNo = start + rowIdx + 1;
|
|
137
|
+
const curRow = [curRecordNo, ...row];
|
|
138
|
+
return React.createElement("tr", {
|
|
139
|
+
key: curRecordNo
|
|
140
|
+
}, curRow.map((col, colIdx) => React.createElement("td", {
|
|
141
|
+
key: colIdx,
|
|
142
|
+
style: {
|
|
143
|
+
// test whether the column is number, and align right
|
|
144
|
+
textAlign: // eslint-disable-next-line no-self-compare
|
|
145
|
+
+col === +col ? 'right' : 'left'
|
|
146
|
+
}
|
|
147
|
+
}, col)));
|
|
148
|
+
}));
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
*
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
const renderViewerTable = () => {
|
|
156
|
+
return React.createElement(StyledViewerTableWrap, {
|
|
157
|
+
ref: tableWrapElRef
|
|
158
|
+
}, React.createElement(StyledViewerTable, null, renderViewerTableThead(), renderViewerTableTbody()));
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
*
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
const renderViewerFooterPagination = () => {
|
|
166
|
+
return React.createElement(Flex, {
|
|
167
|
+
align: "center"
|
|
168
|
+
}, React.createElement(Text, {
|
|
169
|
+
role: "gray1",
|
|
170
|
+
size: "small"
|
|
171
|
+
}, csvRecordCount, " records"), React.createElement(StyledViewerFooterHr, {
|
|
172
|
+
vertical: true,
|
|
173
|
+
color: base.color.navy5
|
|
174
|
+
}), React.createElement(Flex, {
|
|
175
|
+
align: "center"
|
|
176
|
+
}, React.createElement(IconButton, {
|
|
177
|
+
icon: eilArrowLeftwardsDouble,
|
|
178
|
+
role: "navy3",
|
|
179
|
+
size: "nano",
|
|
180
|
+
disabled: page <= 1,
|
|
181
|
+
onClick: () => setPage(1)
|
|
182
|
+
}), React.createElement(Hspace, {
|
|
183
|
+
width: 0.25
|
|
184
|
+
}), React.createElement(IconButton, {
|
|
185
|
+
icon: eilArrowLeftwardsSingle,
|
|
186
|
+
role: "navy3",
|
|
187
|
+
size: "nano",
|
|
188
|
+
disabled: page <= 1,
|
|
189
|
+
onClick: () => setPage(prevPage => prevPage - 1)
|
|
190
|
+
}), React.createElement(Hspace, {
|
|
191
|
+
width: 0.5
|
|
192
|
+
}), React.createElement(Text, {
|
|
193
|
+
role: "gray1",
|
|
194
|
+
size: "small"
|
|
195
|
+
}, page, " / ", maxPage), React.createElement(Hspace, {
|
|
196
|
+
width: 0.5
|
|
197
|
+
}), React.createElement(IconButton, {
|
|
198
|
+
icon: eilArrowRightwardsSingle,
|
|
199
|
+
role: "navy3",
|
|
200
|
+
size: "nano",
|
|
201
|
+
disabled: page >= maxPage,
|
|
202
|
+
onClick: () => setPage(prevPage => prevPage + 1)
|
|
203
|
+
}), React.createElement(Hspace, {
|
|
204
|
+
width: 0.25
|
|
205
|
+
}), React.createElement(IconButton, {
|
|
206
|
+
icon: eilArrowRightwardsDouble,
|
|
207
|
+
role: "navy3",
|
|
208
|
+
size: "nano",
|
|
209
|
+
disabled: page >= maxPage,
|
|
210
|
+
onClick: () => setPage(maxPage)
|
|
211
|
+
})));
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
*
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
const renderViewerFooterSettings = () => {
|
|
219
|
+
return React.createElement(Flex, {
|
|
220
|
+
align: "center"
|
|
221
|
+
}, React.createElement(Flex, {
|
|
222
|
+
align: "center"
|
|
223
|
+
}, React.createElement(Text, {
|
|
224
|
+
role: "gray1",
|
|
225
|
+
size: "small"
|
|
226
|
+
}, React.createElement(FormattedMessage, {
|
|
227
|
+
id: "\uD5E4\uB354 \uC0AC\uC6A9"
|
|
228
|
+
})), React.createElement(Hspace, {
|
|
229
|
+
width: 0.5
|
|
230
|
+
}), React.createElement(AntSwitch, {
|
|
231
|
+
size: "small",
|
|
232
|
+
checked: isUsingHeader,
|
|
233
|
+
onChange: setUsingHeader
|
|
234
|
+
})), React.createElement(StyledViewerFooterHr, {
|
|
235
|
+
vertical: true,
|
|
236
|
+
color: base.color.navy5
|
|
237
|
+
}), React.createElement(Flex, {
|
|
238
|
+
align: "center"
|
|
239
|
+
}, React.createElement(Text, {
|
|
240
|
+
role: "gray1",
|
|
241
|
+
size: "small"
|
|
242
|
+
}, React.createElement(FormattedMessage, {
|
|
243
|
+
id: "\uAD6C\uBD84\uC790:"
|
|
244
|
+
})), React.createElement(Hspace, {
|
|
245
|
+
width: 0.5
|
|
246
|
+
}), React.createElement(StyledViewerFooterSelect, {
|
|
247
|
+
width: 7.5,
|
|
248
|
+
value: delimiter,
|
|
249
|
+
onChange: e => setDelimiter(e.target.value)
|
|
250
|
+
}, OPTIONS_DELIMITER.map(({
|
|
251
|
+
char,
|
|
252
|
+
label
|
|
253
|
+
}) => React.createElement("option", {
|
|
254
|
+
value: char
|
|
255
|
+
}, label)))), React.createElement(StyledViewerFooterHr, {
|
|
256
|
+
vertical: true,
|
|
257
|
+
color: base.color.navy5
|
|
258
|
+
}), React.createElement(Flex, {
|
|
259
|
+
align: "center"
|
|
260
|
+
}, React.createElement(Text, {
|
|
261
|
+
role: "gray1",
|
|
262
|
+
size: "small"
|
|
263
|
+
}, React.createElement(FormattedMessage, {
|
|
264
|
+
id: "\uD398\uC774\uC9C0\uB2F9 \uD589:"
|
|
265
|
+
})), React.createElement(Hspace, {
|
|
266
|
+
width: 0.5
|
|
267
|
+
}), React.createElement(StyledViewerFooterSelect, {
|
|
268
|
+
width: 3,
|
|
269
|
+
value: perPage,
|
|
270
|
+
onChange: e => setPerPage(Number(e.target.value))
|
|
271
|
+
}, OPTIONS_PER_PAGE.map(perPage => React.createElement("option", {
|
|
272
|
+
value: perPage
|
|
273
|
+
}, perPage)))));
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
*
|
|
277
|
+
*/
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
const renderViewerFooter = () => {
|
|
281
|
+
return React.createElement(StyledViewerFooter, null, renderViewerFooterPagination(), renderViewerFooterSettings());
|
|
282
|
+
}; //
|
|
283
|
+
//
|
|
284
|
+
//
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
if (csvStatus === 'idle' || csvStatus === 'pending') {
|
|
29
288
|
return React.createElement(ExerciseFileShimmer, null);
|
|
30
289
|
}
|
|
31
290
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
291
|
+
if (csvStatus === 'rejected') {
|
|
292
|
+
return React.createElement("div", null, "error");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return React.createElement(StyledViewer, null, renderViewerTable(), renderViewerFooter());
|
|
37
296
|
};
|
|
38
297
|
|
|
39
298
|
export { FileViewerCsv as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elice/material-exercise",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.221109.1",
|
|
4
4
|
"description": "User view and editing components of Elice material exercise",
|
|
5
5
|
"repository": "https://git.elicer.io/elice/frontend/library/elice-material",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -81,7 +81,6 @@
|
|
|
81
81
|
"ot-text-unicode": "^4.0.0",
|
|
82
82
|
"prettier": "~2.7.1",
|
|
83
83
|
"random-words": "^1.1.2",
|
|
84
|
-
"react-csv-to-table": "^0.0.4",
|
|
85
84
|
"react-hook-form": "~7.12.0",
|
|
86
85
|
"react-transition-group": "^4.3.0",
|
|
87
86
|
"rxjs": "^7.5.6",
|
|
@@ -116,8 +115,8 @@
|
|
|
116
115
|
"@elice/design-tokens": "^1.220803.0",
|
|
117
116
|
"@elice/icons": "^1.220803.0",
|
|
118
117
|
"@elice/markdown": "^1.220803.0",
|
|
119
|
-
"@elice/material-shared-types": "1.
|
|
120
|
-
"@elice/material-shared-utils": "1.
|
|
118
|
+
"@elice/material-shared-types": "1.221109.1",
|
|
119
|
+
"@elice/material-shared-utils": "1.221109.1",
|
|
121
120
|
"@elice/types": "^1.221027.0",
|
|
122
121
|
"@elice/websocket": "^1.220803.0",
|
|
123
122
|
"@types/classnames": "^2.3.1",
|
|
@@ -139,5 +138,5 @@
|
|
|
139
138
|
"recoil": "^0.6.1",
|
|
140
139
|
"styled-components": "^5.2.0"
|
|
141
140
|
},
|
|
142
|
-
"gitHead": "
|
|
141
|
+
"gitHead": "027b1ce65ecd78b69e79db885699d2e5c373cee6"
|
|
143
142
|
}
|