@@ -10,7 +10,7 @@ import {
10
10
SelectValue ,
11
11
} from "@/components/ui/select" ;
12
12
import { getFormatHandlers } from "@/lib/export-helper" ;
13
- import { useCallback , useEffect , useMemo , useState } from "react" ;
13
+ import { useCallback , useEffect , useState } from "react" ;
14
14
import { Popover , PopoverContent , PopoverTrigger } from "../../ui/popover" ;
15
15
import OptimizeTableState , {
16
16
TableSelectionRange ,
@@ -23,10 +23,31 @@ export type ExportSelection =
23
23
| "selected_row"
24
24
| "selected_col"
25
25
| "selected_range" ;
26
+
27
+ const csvDelimeter = {
28
+ fieldSeparator : "," ,
29
+ lineTerminator : "\\n" ,
30
+ encloser : '"' ,
31
+ nullValue : "NULL" ,
32
+ } ;
33
+ const excelDelimeter = {
34
+ fieldSeparator : "\\t" ,
35
+ lineTerminator : "\\r\\n" ,
36
+ encloser : '"' ,
37
+ nullValue : "NULL" ,
38
+ } ;
39
+
40
+ const textDelimeter = {
41
+ fieldSeparator : "\\t" ,
42
+ lineTerminator : "\\n" ,
43
+ encloser : '"' ,
44
+ nullValue : "NULL" ,
45
+ } ;
26
46
export interface ExportOptions {
27
47
fieldSeparator ?: string ;
28
48
lineTerminator ?: string ;
29
49
encloser ?: string ;
50
+ nullValue ?: string ;
30
51
}
31
52
32
53
interface selectionCount {
@@ -40,99 +61,87 @@ interface ExportSettings {
40
61
target : ExportTarget ;
41
62
selection : ExportSelection ;
42
63
options ?: ExportOptions ;
64
+ formatTemplate ?: Record < string , ExportOptions > ;
43
65
}
44
66
45
67
export default function ExportResultButton ( {
46
68
data,
47
69
} : {
48
70
data : OptimizeTableState ;
49
71
} ) {
50
- const csvDelimeter = useMemo (
51
- ( ) => ( {
52
- fieldSeparator : "," ,
53
- lineTerminator : "\\n" ,
54
- encloser : '"' ,
55
- } ) ,
56
- [ ]
57
- ) ;
58
- const excelDiliemter = {
59
- fieldSeparator : "\\t" ,
60
- lineTerminator : "\\r\\n" ,
61
- encloser : '"' ,
62
- } ;
63
-
64
- const saveSetting = ( settings : ExportSettings ) => {
72
+ const getDefaultOption = useCallback ( ( format : ExportFormat ) => {
73
+ switch ( format ) {
74
+ case "csv" :
75
+ return csvDelimeter ;
76
+ case "xlsx" :
77
+ return excelDelimeter ;
78
+ case "delimited" :
79
+ return textDelimeter ;
80
+ default :
81
+ return null ;
82
+ }
83
+ } , [ ] ) ;
84
+ const saveSettingToStorage = ( settings : ExportSettings ) => {
85
+ settings . formatTemplate = {
86
+ ...settings . formatTemplate ,
87
+ ...( settings . options ? { [ settings . format ] : settings . options } : { } ) ,
88
+ } ;
65
89
localStorage . setItem ( "export_settings" , JSON . stringify ( settings ) ) ;
66
90
} ;
67
91
68
- const exportSettings = useCallback ( ( ) => {
92
+ const getSettingFromStorage = useCallback ( ( ) => {
69
93
const settings = localStorage . getItem ( "export_settings" ) ;
70
94
if ( settings ) {
71
- return JSON . parse ( settings ) as ExportSettings ;
95
+ const settingValue = JSON . parse ( settings ) as ExportSettings ;
96
+ return {
97
+ ...settingValue ,
98
+ options :
99
+ settingValue . formatTemplate ?. [ settingValue . format ] || csvDelimeter ,
100
+ } ;
72
101
}
73
102
return {
74
103
format : "csv" ,
75
104
target : "clipboard" ,
76
105
selection : "complete" ,
77
- options : csvDelimeter ,
106
+ options : getDefaultOption ( "csv" ) ,
78
107
} as ExportSettings ;
79
- } , [ csvDelimeter ] ) ;
108
+ } , [ getDefaultOption ] ) ;
80
109
81
- const [ format , setFormat ] = useState < ExportFormat > ( exportSettings ( ) . format ) ;
82
- const [ exportTarget , setExportTarget ] = useState < ExportTarget > (
83
- exportSettings ( ) . target
110
+ const [ exportSetting , setExportSetting ] = useState < ExportSettings > (
111
+ getSettingFromStorage ( )
84
112
) ;
113
+
85
114
const [ selectionCount , setSelectionCount ] = useState < selectionCount > ( {
86
115
rows : 0 ,
87
116
cols : 0 ,
88
117
ranges : [ ] ,
89
118
} ) ;
90
119
const [ exportSelection , setExportSelection ] = useState < ExportSelection > (
91
120
( ) => {
92
- const savedSelection = exportSettings ( ) . selection ;
121
+ const savedSelection = exportSetting . selection ;
93
122
return validateExportSelection ( savedSelection , selectionCount ) ;
94
123
}
95
124
) ;
96
- const [ delimitedOptions , setDelimitedOptions ] = useState < ExportOptions > (
97
- exportSettings ( ) . options || {
98
- fieldSeparator : "," ,
99
- lineTerminator : "\\n" ,
100
- encloser : '"' ,
101
- }
102
- ) ;
103
- const [ exportOptions , setExportOptions ] = useState < ExportOptions | null > (
104
- ( ) => {
105
- if ( format === "csv" ) {
106
- return csvDelimeter ;
107
- } else if ( format === "xlsx" ) {
108
- return excelDiliemter ;
109
- } else if ( format === "delimited" ) {
110
- return delimitedOptions ;
111
- } else {
112
- return null ;
113
- }
114
- }
115
- ) ;
116
125
117
126
const [ selectedRangeIndex , setSelectedRangeIndex ] = useState < number > (
118
127
selectionCount . ranges . length > 0 ? 0 : - 1
119
128
) ;
120
129
const [ open , setOpen ] = useState ( false ) ;
121
130
122
131
const onExportClicked = useCallback ( ( ) => {
123
- if ( ! format ) return ;
132
+ if ( ! exportSetting . format ) return ;
124
133
125
134
let content = "" ;
126
135
127
136
const formatHandlers = getFormatHandlers (
128
137
data ,
129
- exportTarget ,
138
+ exportSetting . target ,
130
139
exportSelection ,
131
- exportOptions ,
140
+ exportSetting . options ! ,
132
141
selectedRangeIndex
133
142
) ;
134
143
135
- const handler = formatHandlers [ format ] ;
144
+ const handler = formatHandlers [ exportSetting . format ] ;
136
145
if ( handler ) {
137
146
content = handler ( ) ;
138
147
}
@@ -144,15 +153,15 @@ export default function ExportResultButton({
144
153
const url = URL . createObjectURL ( blob ) ;
145
154
const a = document . createElement ( "a" ) ;
146
155
a . href = url ;
147
- a . download = `export.${ format === "delimited" ? "csv" : format } ` ;
156
+ a . download = `export.${ exportSetting . format === "delimited" ? "csv" : exportSetting . format } ` ;
148
157
a . click ( ) ;
149
158
URL . revokeObjectURL ( url ) ;
150
159
} , [
151
- format ,
160
+ exportSetting . format ,
161
+ exportSetting . target ,
162
+ exportSetting . options ,
152
163
data ,
153
- exportTarget ,
154
164
exportSelection ,
155
- exportOptions ,
156
165
selectedRangeIndex ,
157
166
] ) ;
158
167
@@ -173,32 +182,13 @@ export default function ExportResultButton({
173
182
174
183
useEffect ( ( ) => {
175
184
setExportSelection (
176
- validateExportSelection ( exportSettings ( ) . selection , selectionCount )
185
+ validateExportSelection ( exportSetting . selection , selectionCount )
177
186
) ;
178
- } , [ exportSettings , selectionCount ] ) ;
187
+ } , [ exportSetting , selectionCount ] ) ;
179
188
180
189
useEffect ( ( ) => {
181
- saveSetting ( {
182
- ...exportSettings ( ) ,
183
- format,
184
- selection : exportSelection ,
185
- target : exportTarget ,
186
- } ) ;
187
- if ( format === "delimited" ) {
188
- saveSetting ( {
189
- ...exportSettings ( ) ,
190
- options : exportOptions ?? csvDelimeter ,
191
- } ) ;
192
- if ( exportOptions ) setDelimitedOptions ( exportOptions ) ;
193
- }
194
- } , [
195
- csvDelimeter ,
196
- exportOptions ,
197
- exportSelection ,
198
- exportSettings ,
199
- exportTarget ,
200
- format ,
201
- ] ) ;
190
+ saveSettingToStorage ( exportSetting ) ;
191
+ } , [ exportSelection , exportSetting ] ) ;
202
192
203
193
const SelectedRange = ( {
204
194
ranges,
@@ -244,9 +234,12 @@ export default function ExportResultButton({
244
234
245
235
< RadioGroup
246
236
className = "flex gap-4"
247
- defaultValue = { exportTarget }
237
+ defaultValue = { exportSetting . target }
248
238
onValueChange = { ( e ) => {
249
- setExportTarget ( e as ExportTarget ) ;
239
+ setExportSetting ( ( prev ) => ( {
240
+ ...prev ,
241
+ target : e as ExportTarget ,
242
+ } ) ) ;
250
243
} }
251
244
>
252
245
< div className = "flex items-center space-x-2" >
@@ -265,18 +258,17 @@ export default function ExportResultButton({
265
258
< small > Output format</ small >
266
259
< RadioGroup
267
260
className = "mt-2 flex flex-col gap-3"
268
- defaultValue = { format }
261
+ defaultValue = { exportSetting . format }
269
262
onValueChange = { ( e ) => {
270
- setFormat ( e as ExportFormat ) ;
271
- if ( e === "csv" ) {
272
- setExportOptions ( csvDelimeter ) ;
273
- } else if ( e === "xlsx" ) {
274
- setExportOptions ( excelDiliemter ) ;
275
- } else if ( e === "delimited" ) {
276
- setExportOptions ( delimitedOptions ) ;
277
- } else {
278
- setExportOptions ( null ) ;
279
- }
263
+ setExportSetting ( ( prev ) => ( {
264
+ ...prev ,
265
+ format : e as ExportFormat ,
266
+ options : exportSetting . formatTemplate ?. [
267
+ e as ExportFormat
268
+ ] || {
269
+ ...getDefaultOption ( e as ExportFormat ) ,
270
+ } ,
271
+ } ) ) ;
280
272
} }
281
273
>
282
274
< div className = "flex items-center space-x-2" >
@@ -415,14 +407,17 @@ export default function ExportResultButton({
415
407
< span className = "w-[120px] text-sm" > Field separator:</ span >
416
408
< div className = "flex h-[28px] w-[120px] items-center rounded-md bg-white px-3 py-2.5 text-base text-neutral-900 outline outline-1 outline-neutral-200 focus:outline-neutral-400/70 dark:bg-neutral-900 dark:text-white dark:outline-neutral-800 dark:focus:outline-neutral-600" >
417
409
< input
418
- disabled = { format !== "delimited" }
410
+ disabled = { exportSetting . format !== "delimited" }
419
411
type = "text"
420
412
className = "flex-1 bg-transparent text-sm font-light outline-hidden"
421
- value = { exportOptions ?. fieldSeparator || "" }
413
+ value = { exportSetting . options ?. fieldSeparator || "" }
422
414
onChange = { ( e ) => {
423
- setExportOptions ( {
424
- ...exportOptions ,
425
- fieldSeparator : e . target . value ,
415
+ setExportSetting ( {
416
+ ...exportSetting ,
417
+ options : {
418
+ ...exportSetting . options ,
419
+ fieldSeparator : e . target . value ,
420
+ } ,
426
421
} ) ;
427
422
} }
428
423
/>
@@ -432,14 +427,17 @@ export default function ExportResultButton({
432
427
< span className = "w-[120px] text-sm" > Line terminator:</ span >
433
428
< div className = "flex h-[28px] w-[120px] items-center rounded-md bg-white px-3 py-2.5 text-base text-neutral-900 outline outline-1 outline-neutral-200 focus:outline-neutral-400/70 dark:bg-neutral-900 dark:text-white dark:outline-neutral-800 dark:focus:outline-neutral-600" >
434
429
< input
435
- disabled = { format !== "delimited" }
430
+ disabled = { exportSetting . format !== "delimited" }
436
431
type = "text"
437
432
className = "flex-1 bg-transparent text-sm font-light outline-hidden"
438
- value = { exportOptions ?. lineTerminator || "" }
433
+ value = { exportSetting . options ?. lineTerminator || "" }
439
434
onChange = { ( e ) => {
440
- setExportOptions ( {
441
- ...exportOptions ,
442
- lineTerminator : e . target . value ,
435
+ setExportSetting ( {
436
+ ...exportSetting ,
437
+ options : {
438
+ ...exportSetting . options ,
439
+ lineTerminator : e . target . value ,
440
+ } ,
443
441
} ) ;
444
442
} }
445
443
/>
@@ -450,14 +448,36 @@ export default function ExportResultButton({
450
448
< span className = "w-[120px] text-sm" > Encloser:</ span >
451
449
< div className = "flex h-[28px] w-[120px] items-center rounded-md bg-white px-3 py-2.5 text-base text-neutral-900 outline outline-1 outline-neutral-200 focus:outline-neutral-400/70 dark:bg-neutral-900 dark:text-white dark:outline-neutral-800 dark:focus:outline-neutral-600" >
452
450
< input
453
- disabled = { format !== "delimited" }
451
+ disabled = { exportSetting . format !== "delimited" }
452
+ type = "text"
453
+ className = "flex-1 bg-transparent text-sm font-light outline-hidden"
454
+ value = { exportSetting . options ?. encloser || "" }
455
+ onChange = { ( e ) => {
456
+ setExportSetting ( {
457
+ ...exportSetting ,
458
+ options : {
459
+ ...exportSetting . options ,
460
+ encloser : e . target . value ,
461
+ } ,
462
+ } ) ;
463
+ } }
464
+ />
465
+ </ div >
466
+ </ div >
467
+ < div className = "flex items-center space-x-4" >
468
+ < span className = "w-[120px] text-sm" > NULL Value:</ span >
469
+ < div className = "flex h-[28px] w-[120px] items-center rounded-md bg-white px-3 py-2.5 text-base text-neutral-900 outline outline-1 outline-neutral-200 focus:outline-neutral-400/70 dark:bg-neutral-900 dark:text-white dark:outline-neutral-800 dark:focus:outline-neutral-600" >
470
+ < input
454
471
type = "text"
455
472
className = "flex-1 bg-transparent text-sm font-light outline-hidden"
456
- value = { exportOptions ?. encloser || "" }
473
+ value = { exportSetting . options ?. nullValue || "" }
457
474
onChange = { ( e ) => {
458
- setExportOptions ( {
459
- ...exportOptions ,
460
- encloser : e . target . value ,
475
+ setExportSetting ( {
476
+ ...exportSetting ,
477
+ options : {
478
+ ...exportSetting . options ,
479
+ nullValue : e . target . value ,
480
+ } ,
461
481
} ) ;
462
482
} }
463
483
/>
0 commit comments