@@ -10,7 +10,7 @@ import {
1010 SelectValue ,
1111} from "@/components/ui/select" ;
1212import { getFormatHandlers } from "@/lib/export-helper" ;
13- import { useCallback , useEffect , useMemo , useState } from "react" ;
13+ import { useCallback , useEffect , useState } from "react" ;
1414import { Popover , PopoverContent , PopoverTrigger } from "../../ui/popover" ;
1515import OptimizeTableState , {
1616 TableSelectionRange ,
@@ -23,10 +23,31 @@ export type ExportSelection =
2323 | "selected_row"
2424 | "selected_col"
2525 | "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+ } ;
2646export interface ExportOptions {
2747 fieldSeparator ?: string ;
2848 lineTerminator ?: string ;
2949 encloser ?: string ;
50+ nullValue ?: string ;
3051}
3152
3253interface selectionCount {
@@ -40,99 +61,87 @@ interface ExportSettings {
4061 target : ExportTarget ;
4162 selection : ExportSelection ;
4263 options ?: ExportOptions ;
64+ formatTemplate ?: Record < string , ExportOptions > ;
4365}
4466
4567export default function ExportResultButton ( {
4668 data,
4769} : {
4870 data : OptimizeTableState ;
4971} ) {
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+ } ;
6589 localStorage . setItem ( "export_settings" , JSON . stringify ( settings ) ) ;
6690 } ;
6791
68- const exportSettings = useCallback ( ( ) => {
92+ const getSettingFromStorage = useCallback ( ( ) => {
6993 const settings = localStorage . getItem ( "export_settings" ) ;
7094 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+ } ;
72101 }
73102 return {
74103 format : "csv" ,
75104 target : "clipboard" ,
76105 selection : "complete" ,
77- options : csvDelimeter ,
106+ options : getDefaultOption ( "csv" ) ,
78107 } as ExportSettings ;
79- } , [ csvDelimeter ] ) ;
108+ } , [ getDefaultOption ] ) ;
80109
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 ( )
84112 ) ;
113+
85114 const [ selectionCount , setSelectionCount ] = useState < selectionCount > ( {
86115 rows : 0 ,
87116 cols : 0 ,
88117 ranges : [ ] ,
89118 } ) ;
90119 const [ exportSelection , setExportSelection ] = useState < ExportSelection > (
91120 ( ) => {
92- const savedSelection = exportSettings ( ) . selection ;
121+ const savedSelection = exportSetting . selection ;
93122 return validateExportSelection ( savedSelection , selectionCount ) ;
94123 }
95124 ) ;
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- ) ;
116125
117126 const [ selectedRangeIndex , setSelectedRangeIndex ] = useState < number > (
118127 selectionCount . ranges . length > 0 ? 0 : - 1
119128 ) ;
120129 const [ open , setOpen ] = useState ( false ) ;
121130
122131 const onExportClicked = useCallback ( ( ) => {
123- if ( ! format ) return ;
132+ if ( ! exportSetting . format ) return ;
124133
125134 let content = "" ;
126135
127136 const formatHandlers = getFormatHandlers (
128137 data ,
129- exportTarget ,
138+ exportSetting . target ,
130139 exportSelection ,
131- exportOptions ,
140+ exportSetting . options ! ,
132141 selectedRangeIndex
133142 ) ;
134143
135- const handler = formatHandlers [ format ] ;
144+ const handler = formatHandlers [ exportSetting . format ] ;
136145 if ( handler ) {
137146 content = handler ( ) ;
138147 }
@@ -144,15 +153,15 @@ export default function ExportResultButton({
144153 const url = URL . createObjectURL ( blob ) ;
145154 const a = document . createElement ( "a" ) ;
146155 a . href = url ;
147- a . download = `export.${ format === "delimited" ? "csv" : format } ` ;
156+ a . download = `export.${ exportSetting . format === "delimited" ? "csv" : exportSetting . format } ` ;
148157 a . click ( ) ;
149158 URL . revokeObjectURL ( url ) ;
150159 } , [
151- format ,
160+ exportSetting . format ,
161+ exportSetting . target ,
162+ exportSetting . options ,
152163 data ,
153- exportTarget ,
154164 exportSelection ,
155- exportOptions ,
156165 selectedRangeIndex ,
157166 ] ) ;
158167
@@ -173,32 +182,13 @@ export default function ExportResultButton({
173182
174183 useEffect ( ( ) => {
175184 setExportSelection (
176- validateExportSelection ( exportSettings ( ) . selection , selectionCount )
185+ validateExportSelection ( exportSetting . selection , selectionCount )
177186 ) ;
178- } , [ exportSettings , selectionCount ] ) ;
187+ } , [ exportSetting , selectionCount ] ) ;
179188
180189 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 ] ) ;
202192
203193 const SelectedRange = ( {
204194 ranges,
@@ -244,9 +234,12 @@ export default function ExportResultButton({
244234
245235 < RadioGroup
246236 className = "flex gap-4"
247- defaultValue = { exportTarget }
237+ defaultValue = { exportSetting . target }
248238 onValueChange = { ( e ) => {
249- setExportTarget ( e as ExportTarget ) ;
239+ setExportSetting ( ( prev ) => ( {
240+ ...prev ,
241+ target : e as ExportTarget ,
242+ } ) ) ;
250243 } }
251244 >
252245 < div className = "flex items-center space-x-2" >
@@ -265,18 +258,17 @@ export default function ExportResultButton({
265258 < small > Output format</ small >
266259 < RadioGroup
267260 className = "mt-2 flex flex-col gap-3"
268- defaultValue = { format }
261+ defaultValue = { exportSetting . format }
269262 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+ } ) ) ;
280272 } }
281273 >
282274 < div className = "flex items-center space-x-2" >
@@ -415,14 +407,17 @@ export default function ExportResultButton({
415407 < span className = "w-[120px] text-sm" > Field separator:</ span >
416408 < 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" >
417409 < input
418- disabled = { format !== "delimited" }
410+ disabled = { exportSetting . format !== "delimited" }
419411 type = "text"
420412 className = "flex-1 bg-transparent text-sm font-light outline-hidden"
421- value = { exportOptions ?. fieldSeparator || "" }
413+ value = { exportSetting . options ?. fieldSeparator || "" }
422414 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+ } ,
426421 } ) ;
427422 } }
428423 />
@@ -432,14 +427,17 @@ export default function ExportResultButton({
432427 < span className = "w-[120px] text-sm" > Line terminator:</ span >
433428 < 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" >
434429 < input
435- disabled = { format !== "delimited" }
430+ disabled = { exportSetting . format !== "delimited" }
436431 type = "text"
437432 className = "flex-1 bg-transparent text-sm font-light outline-hidden"
438- value = { exportOptions ?. lineTerminator || "" }
433+ value = { exportSetting . options ?. lineTerminator || "" }
439434 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+ } ,
443441 } ) ;
444442 } }
445443 />
@@ -450,14 +448,36 @@ export default function ExportResultButton({
450448 < span className = "w-[120px] text-sm" > Encloser:</ span >
451449 < 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" >
452450 < 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
454471 type = "text"
455472 className = "flex-1 bg-transparent text-sm font-light outline-hidden"
456- value = { exportOptions ?. encloser || "" }
473+ value = { exportSetting . options ?. nullValue || "" }
457474 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+ } ,
461481 } ) ;
462482 } }
463483 />
0 commit comments