99 <option value =" ;" >{{ $t("Semicolon") }}</option >
1010 </select >
1111
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+
1269 <button
1370 data-uitest =" download_csv_button"
1471 :disabled =" buttonDisabled"
2582
2683<script setup lang="ts">
2784import axios from " axios"
28- import { ref , inject } from " vue"
85+ import { ref , inject , computed , onMounted , watch } from " vue"
2986import type { I18n } from " vue-i18n"
3087
31- import type { RowCountAPIResponse } from " ../interfaces"
88+ import type { RowCountAPIResponse , Schema } from " ../interfaces"
3289import { getOrderByString } from " @/utils"
3390import Modal from " ./Modal.vue"
3491import { useStore } from " vuex"
@@ -44,6 +101,58 @@ const i18n = inject<I18n>("i18n")
44101
45102/** ***************************************************************************/
46103
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+
47156// Just in case `replaceAll` isn't supported by the browser, provide a
48157// fallback.
49158const replaceAll = (input : string , value : string , newValue : string ): string => {
@@ -64,38 +173,84 @@ const translate = (term: string): string => {
64173
65174/** ***************************************************************************/
66175
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+
67180const fetchExportedRows = async () => {
68181 buttonDisabled .value = true
69182
70183 const params = store .state .filterParams
71- const orderBy = store .state .orderBy
72184 const tableName = store .state .currentTableName
73185
74- if (orderBy && orderBy .length > 0 ) {
75- params [" __order" ] = getOrderByString (orderBy )
76- }
77- // Get the row counts:
186+ /** ***********************************************************************/
187+ // Get the row count
188+
78189 const response = await axios .get (` api/tables/${tableName }/count/ ` , {
79190 params
80191 })
81192 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+
82208 const localParams = { ... params }
83209
84- localParams [" __page" ] = data .count
85210 // Set higher __page_size param to have fewer requests to the API:
86211 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+
88246 const exportedRows = []
89247
90248 try {
91249 for (let i = 1 ; i < pages + 1 ; i ++ ) {
92250 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+ })
99254 exportedRows .push (... response .data .rows )
100255 }
101256 let data: string = " "
@@ -138,4 +293,50 @@ const fetchExportedRows = async () => {
138293p .note {
139294 font-size : 0.85em ;
140295}
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+ }
141342 </style >
0 commit comments