9
9
<option value =" ;" >{{ $t("Semicolon") }}</option >
10
10
</select >
11
11
12
+ <p id =" column_header" >
13
+ <a href =" #" @click.prevent =" showColumnTable = !showColumnTable" >
14
+ <font-awesome-icon
15
+ :icon =" showColumnTable ? 'angle-down' : 'angle-right'"
16
+ />
17
+ Columns</a
18
+ ><a
19
+ id =" toggle_all"
20
+ href =" #"
21
+ @click.prevent =" toggleAll()"
22
+ v-show =" showColumnTable"
23
+ >Toggle all</a
24
+ >
25
+ </p >
26
+
27
+ <table class =" column_list" v-show =" showColumnTable" >
28
+ <tbody >
29
+ <tr v-for =" columnName in allColumnNames" >
30
+ <td >
31
+ <label :for =" 'csv_' + columnName" >{{
32
+ columnName
33
+ }}</label >
34
+
35
+ <input
36
+ type =" checkbox"
37
+ :id =" 'csv_' + columnName"
38
+ :checked ="
39
+ selectedColumns.indexOf(columnName) != -1
40
+ "
41
+ @change ="
42
+ toggleValue(
43
+ ($event.target as HTMLInputElement)
44
+ .checked,
45
+ columnName
46
+ )
47
+ "
48
+ />
49
+ </td >
50
+ </tr >
51
+ </tbody >
52
+
53
+ <tfoot >
54
+ <tr >
55
+ <td >
56
+ <label for =" include_readable"
57
+ >Include readable</label
58
+ >
59
+ <input
60
+ type =" checkbox"
61
+ id =" include_readable"
62
+ v-model =" includeReadable"
63
+ />
64
+ </td >
65
+ </tr >
66
+ </tfoot >
67
+ </table >
68
+
12
69
<button
13
70
data-uitest =" download_csv_button"
14
71
:disabled =" buttonDisabled"
25
82
26
83
<script setup lang="ts">
27
84
import axios from " axios"
28
- import { ref , inject } from " vue"
85
+ import { ref , inject , computed , onMounted , watch } from " vue"
29
86
import type { I18n } from " vue-i18n"
30
87
31
- import type { RowCountAPIResponse } from " ../interfaces"
88
+ import type { RowCountAPIResponse , Schema } from " ../interfaces"
32
89
import { getOrderByString } from " @/utils"
33
90
import Modal from " ./Modal.vue"
34
91
import { useStore } from " vuex"
@@ -44,6 +101,58 @@ const i18n = inject<I18n>("i18n")
44
101
45
102
/** ***************************************************************************/
46
103
104
+ const schema = computed ((): Schema => {
105
+ return store .state .schema
106
+ })
107
+
108
+ /** ***************************************************************************/
109
+
110
+ const showColumnTable = ref <boolean >(false )
111
+
112
+ /** ***************************************************************************/
113
+
114
+ const selectedColumns = ref <string []>([])
115
+ const includeReadable = ref <boolean >(true )
116
+
117
+ const toggleAll = () => {
118
+ if (selectedColumns .value .length == 0 ) {
119
+ selectedColumns .value = allColumnNames .value
120
+ } else {
121
+ selectedColumns .value = []
122
+ }
123
+ }
124
+
125
+ const toggleValue = (checked : boolean , columnName : string ) => {
126
+ if (checked ) {
127
+ selectedColumns .value .push (columnName )
128
+ } else {
129
+ selectedColumns .value = selectedColumns .value .filter (
130
+ (i ) => i != columnName
131
+ )
132
+ }
133
+ }
134
+
135
+ const allColumnNames = computed (() => {
136
+ let columnNames = Object .keys (schema .value .properties )
137
+ const primaryKeyName = schema .value .extra .primary_key_name
138
+
139
+ if (columnNames .indexOf (primaryKeyName ) == - 1 ) {
140
+ columnNames = [primaryKeyName , ... columnNames ]
141
+ }
142
+
143
+ return columnNames
144
+ })
145
+
146
+ const setupInitialColumns = () => {
147
+ selectedColumns .value = schema .value .extra .visible_column_names
148
+ }
149
+
150
+ watch (schema , setupInitialColumns )
151
+
152
+ onMounted (setupInitialColumns )
153
+
154
+ /** ***************************************************************************/
155
+
47
156
// Just in case `replaceAll` isn't supported by the browser, provide a
48
157
// fallback.
49
158
const replaceAll = (input : string , value : string , newValue : string ): string => {
@@ -64,38 +173,84 @@ const translate = (term: string): string => {
64
173
65
174
/** ***************************************************************************/
66
175
176
+ // We allow the user to download more than this number of rows, but we ask them
177
+ // to confirm first.
178
+ const softRowLimit = 10000
179
+
67
180
const fetchExportedRows = async () => {
68
181
buttonDisabled .value = true
69
182
70
183
const params = store .state .filterParams
71
- const orderBy = store .state .orderBy
72
184
const tableName = store .state .currentTableName
73
185
74
- if (orderBy && orderBy .length > 0 ) {
75
- params [" __order" ] = getOrderByString (orderBy )
76
- }
77
- // Get the row counts:
186
+ /** ***********************************************************************/
187
+ // Get the row count
188
+
78
189
const response = await axios .get (` api/tables/${tableName }/count/ ` , {
79
190
params
80
191
})
81
192
const data = response .data as RowCountAPIResponse
193
+ const rowCount = data .count
194
+
195
+ if (
196
+ rowCount > softRowLimit &&
197
+ ! confirm (
198
+ ` There are more than ${softRowLimit }, are you sure you want to continue? `
199
+ )
200
+ ) {
201
+ return
202
+ }
203
+
204
+ /** ***********************************************************************/
205
+ // Work out how many requests we need to make (based on the row count).
206
+ // If there are lots of rows, we need to make multiple requests.
207
+
82
208
const localParams = { ... params }
83
209
84
- localParams [" __page" ] = data .count
85
210
// Set higher __page_size param to have fewer requests to the API:
86
211
localParams [" __page_size" ] = 1000
87
- const pages = Math .ceil (data .count / localParams [" __page_size" ])
212
+
213
+ const pages = Math .ceil (rowCount / localParams [" __page_size" ])
214
+
215
+ /** ***********************************************************************/
216
+ // Make sure orderBy is included in the query, so it matches how the
217
+ // results are currently displayed.
218
+
219
+ const orderBy = store .state .orderBy
220
+
221
+ if (orderBy && orderBy .length > 0 ) {
222
+ localParams [" __order" ] = getOrderByString (orderBy )
223
+ }
224
+
225
+ /** ***********************************************************************/
226
+ // Work out which columns to fetch
227
+
228
+ if (selectedColumns .value .length == 0 ) {
229
+ alert (" Please select at least one column." )
230
+ return
231
+ }
232
+
233
+ if (selectedColumns .value .length != allColumnNames .value .length ) {
234
+ // If only some columns are selected, we need to filter which are
235
+ // returned.
236
+ localParams [" __visible_fields" ] = selectedColumns .value .join (" ," )
237
+ }
238
+
239
+ /** ***********************************************************************/
240
+ // Add readable if required
241
+
242
+ localParams [" __readable" ] = true
243
+
244
+ /** ***********************************************************************/
245
+
88
246
const exportedRows = []
89
247
90
248
try {
91
249
for (let i = 1 ; i < pages + 1 ; i ++ ) {
92
250
localParams [" __page" ] = i
93
- const response = await axios .get (
94
- ` api/tables/${tableName }/?__readable=true ` ,
95
- {
96
- params: localParams
97
- }
98
- )
251
+ const response = await axios .get (` api/tables/${tableName }/ ` , {
252
+ params: localParams
253
+ })
99
254
exportedRows .push (... response .data .rows )
100
255
}
101
256
let data: string = " "
@@ -138,4 +293,50 @@ const fetchExportedRows = async () => {
138
293
p .note {
139
294
font-size : 0.85em ;
140
295
}
296
+
297
+ p #column_header {
298
+ margin-bottom : 0 ;
299
+
300
+ a {
301
+ text-decoration : none ;
302
+
303
+ & #toggle_all {
304
+ text-decoration : none ;
305
+ font-size : 0.8em ;
306
+ float : right ;
307
+ }
308
+ }
309
+ }
310
+
311
+ table .column_list {
312
+ width : 100% ;
313
+
314
+ tr {
315
+ td {
316
+ box-sizing : border-box ;
317
+ padding : 0.5rem ;
318
+ display : flex ;
319
+ flex-direction : row ;
320
+
321
+ label {
322
+ flex-grow : 1 ;
323
+ padding : 0 ;
324
+ }
325
+
326
+ input {
327
+ flex-grow : 0 ;
328
+ }
329
+
330
+ a {
331
+ text-decoration : none ;
332
+ }
333
+ }
334
+ }
335
+
336
+ tfoot {
337
+ td {
338
+ margin-top : 1rem ;
339
+ }
340
+ }
341
+ }
141
342
</style >
0 commit comments