PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
← All changes | admin/js/export/screen.jsx +130 -117 2.4.1 → 3.4 View file →
@@ -6,16 +6,19 @@
6 6 * @author Tobias Bäthge
7 7 * @since 2.2.0
8 8 */
9 9
10 -/* globals tp */
11 -
12 10 /**
13 11 * WordPress dependencies.
14 12 */
15 -import { useState } from 'react';
13 +import { useEffect, useRef, useState } from 'react';
16 14 import {
15 + Button,
16 + CheckboxControl,
17 + __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
17 18 Icon,
19 + SelectControl,
20 + __experimentalVStack as VStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
18 21 } from '@wordpress/components';
19 22 import { info } from '@wordpress/icons';
20 23 import { __, _x, sprintf } from '@wordpress/i18n';
21 24
@@ -26,24 +29,24 @@
26 29 let exportTablesSelectSize = tablesCount + 1;
27 30 const maxExportTablesSelectSize = 12;
28 31 exportTablesSelectSize = Math.max( exportTablesSelectSize, 3 );
29 32 exportTablesSelectSize = Math.min( exportTablesSelectSize, maxExportTablesSelectSize );
33 +const exportTablesSelectMultiple = tp.export.zipSupportAvailable;
30 34
31 -// The <option> entries for the dropdowns do not depend on the state, so they can be created once.
32 35 const tablesSelectOptions = Object.entries( tp.export.tables ).map( ( [ tableId, tableName ] ) => {
33 36 if ( '' === tableName.trim() ) {
34 37 tableName = __( '(no name)', 'tablepress' );
35 38 }
36 39 const optionText = sprintf( __( 'ID %1$s: %2$s', 'tablepress' ), tableId, tableName );
37 - return <option key={ tableId } value={ tableId }>{ optionText }</option>;
40 + return { value: tableId, label: optionText };
38 41 } );
39 -const exportFormatsSelectOptions = Object.entries( tp.export.exportFormats ).map( ( [ exportFormat, exportFormatName ] ) =>
40 - <option key={ exportFormat } value={ exportFormat }>{ exportFormatName }</option>
41 -);
42 -const csvDelimitersSelectOptions = Object.entries( tp.export.csvDelimiters ).map( ( [ csvDelimiter, csvDelimiterName ] ) =>
43 - <option key={ csvDelimiter } value={ csvDelimiter }>{ csvDelimiterName }</option>
44 -);
42 +if ( ! exportTablesSelectMultiple ) {
43 + tablesSelectOptions.unshift( { value: '', label: __( '— Select —', 'tablepress' ), disabled: true } );
44 +}
45 45
46 +const exportFormatsSelectOptions = Object.entries( tp.export.exportFormats ).map( ( [ exportFormat, exportFormatName ] ) => ( { value: exportFormat, label: exportFormatName } ) );
47 +const csvDelimitersSelectOptions = Object.entries( tp.export.csvDelimiters ).map( ( [ csvDelimiter, csvDelimiterName ] ) => ( { value: csvDelimiter, label: csvDelimiterName } ) );
48 +
46 49 /**
47 50 * Returns the "Export Screen" component's JSX markup.
48 51 *
49 52 * @return {Object} Export Screen component.
@@ -48,8 +51,9 @@
48 51 *
49 52 * @return {Object} Export Screen component.
50 53 */
51 54 const Screen = () => {
55 + const tablesExportListSelect = useRef( null );
52 56 const [ screenData, setScreenData ] = useState( {
53 57 selectedTables: tp.export.selectedTables,
54 58 exportFormat: tp.export.exportFormat,
55 59 csvDelimiter: tp.export.csvDelimiter,
@@ -62,95 +66,94 @@
62 66
63 67 /**
64 68 * Handles screen data state changes.
65 69 *
66 - * @param {Object} updatedData Data in the screen data state that should be updated.
70 + * @param {Object} updatedScreenData Data in the screen data state that should be updated.
67 71 */
68 - const updateScreenData = ( updatedData ) => {
69 - const newScreenData = {
70 - ...screenData,
71 - ...updatedData,
72 - };
73 - setScreenData( newScreenData );
72 + const updateScreenData = ( updatedScreenData ) => {
73 + setScreenData( ( currentScreenData ) => ( {
74 + ...currentScreenData,
75 + ...updatedScreenData,
76 + } ) );
74 77 };
75 78
79 + /*
80 + * Set the size of the export tables dropdown to the number of tables, if ZIP file support is available.
81 + * `SelectControl` does not support the HTML `size` attribute, so that this has to be done manually with an effect.
82 + */
83 + useEffect( () => {
84 + tablesExportListSelect.current.size = exportTablesSelectMultiple ? exportTablesSelectSize : 1;
85 + }, [] );
86 +
76 87 return (
77 88 <table className="tablepress-postbox-table fixed">
78 89 <tbody>
79 90 <tr>
80 91 <th className="column-1 top-align" scope="row">
81 - <label htmlFor="tables-export-list">
82 - { __( 'Tables to Export', 'tablepress' ) }:
83 - </label>
84 - { tp.export.zipSupportAvailable &&
85 - <>
86 - {
87 - // Show a "Select all" checkbox to select all entries in the export tables dropdown.
88 - }
89 - <br /><br />
90 - <label htmlFor="tables-export-select-all">
91 - <input
92 - type="checkbox"
93 - id="tables-export-select-all"
92 + <VStack
93 + spacing="20px"
94 + >
95 + <label htmlFor="tables-export-list">
96 + { __( 'Tables to Export', 'tablepress' ) }:
97 + </label>
98 + { exportTablesSelectMultiple &&
99 + <VStack>
100 + <CheckboxControl
101 + // Show a "Select all" checkbox to select all entries in the export tables dropdown.
102 + __nextHasNoMarginBottom
103 + label={ __( 'Select all', 'tablepress' ) }
94 104 checked={ screenData.selectedTables.length === tablesCount }
95 105 onChange={ () => {
96 106 const selectedTables = ( screenData.selectedTables.length === tablesCount ) ? [] : Object.keys( tp.export.tables );
97 107 updateScreenData( { selectedTables } );
98 108 } }
99 - /> { __( 'Select all', 'tablepress' ) }
100 - </label>
101 - { tablesCount > maxExportTablesSelectSize &&
102 - <>
103 - {
109 + />
110 + { tablesCount > maxExportTablesSelectSize &&
111 + <CheckboxControl
104 112 // Show a "Reverse List" checkbox if more tables are shown than what the height of the export tables dropdown holds.
105 - }
106 - <br /><br />
107 - <label htmlFor="tables-export-reverse-list">
108 - <input
109 - id="tables-export-reverse-list"
110 - type="checkbox"
111 - checked={ screenData.reverseList }
112 - onChange={ () => {
113 - updateScreenData( { reverseList: ! screenData.reverseList } );
114 - tablesSelectOptions.reverse();
115 - } }
116 - /> { __( 'Reverse list', 'tablepress' ) }
117 - </label>
118 - </>
119 - }
120 - </>
121 - }
113 + __nextHasNoMarginBottom
114 + label={ __( 'Reverse list', 'tablepress' ) }
115 + checked={ screenData.reverseList }
116 + onChange={ ( reverseList ) => {
117 + updateScreenData( { reverseList } );
118 + tablesSelectOptions.reverse();
119 + } }
120 + />
121 + }
122 + </VStack>
123 + }
124 + </VStack>
122 125 </th>
123 126 <td className="column-2">
124 - <select
127 + <SelectControl
128 + __nextHasNoMarginBottom
129 + __next40pxDefaultSize
130 + ref={ tablesExportListSelect }
125 131 id="tables-export-list"
126 - size={ tp.export.zipSupportAvailable ? exportTablesSelectSize : 1 }
127 - multiple={ tp.export.zipSupportAvailable }
128 - value={ screenData.selectedTables }
129 - onChange={ ( event ) => {
130 - const selectedTables = [ ...event.target.selectedOptions ].map( ( option ) => option.value );
132 + // size={ exportTablesSelectMultiple ? exportTablesSelectSize : 1 } // Not supported by `SelectControl`, so done with an effect above.
133 + multiple={ exportTablesSelectMultiple }
134 + value={ exportTablesSelectMultiple ? screenData.selectedTables : ( screenData.selectedTables[0] ?? '' ) }
135 + onChange={ ( selectedTables ) => {
136 + if ( 'string' === typeof selectedTables ) {
137 + selectedTables = [ selectedTables ];
138 + }
131 139 updateScreenData( { selectedTables } );
132 140 } }
133 - style={ {
134 - width: '100%',
135 - } }
136 - >
137 - { tablesSelectOptions }
138 - </select>
139 - { tp.export.zipSupportAvailable &&
140 - <>
141 - <br />
142 - <p className="info-text">
143 - <Icon icon={ info } />
144 - <span>
145 - { sprintf(
146 - __( 'You can select multiple tables by holding down the “%1$s” key or the “%2$s” key for ranges.', 'tablepress' ),
147 - window?.navigator?.platform?.includes( 'Mac' ) ? _x( '⌘', 'keyboard shortcut modifier key on a Mac keyboard', 'tablepress' ) : _x( 'Ctrl', 'keyboard key', 'tablepress' ),
148 - _x( 'Shift', 'keyboard key', 'tablepress' ) )
149 - }
150 - </span>
151 - </p>
152 - </>
141 + options={ tablesSelectOptions }
142 + />
143 + { exportTablesSelectMultiple &&
144 + <HStack
145 + alignment="left"
146 + >
147 + <Icon icon={ info } />
148 + <span>
149 + { sprintf(
150 + __( 'You can select multiple tables by holding down the “%1$s” key or the “%2$s” key for ranges.', 'tablepress' ),
151 + window?.navigator?.platform?.includes( 'Mac' ) ? _x( '⌘', 'keyboard shortcut modifier key on a Mac keyboard', 'tablepress' ) : _x( 'Ctrl', 'keyboard key', 'tablepress' ),
152 + _x( 'Shift', 'keyboard key', 'tablepress' ) )
153 + }
154 + </span>
155 + </HStack>
153 156 }
154 157 </td>
155 158 </tr>
156 159 <tr>
@@ -159,16 +162,21 @@
159 162 { __( 'Export Format', 'tablepress' ) }:
160 163 </label>
161 164 </th>
162 165 <td className="column-2">
163 - <select
164 - id="tables-export-format"
165 - name="export[format]"
166 - value={ screenData.exportFormat }
167 - onChange={ ( event ) => updateScreenData( { exportFormat: event.target.value } ) }
168 - >
169 - { exportFormatsSelectOptions }
170 - </select>
166 + <HStack>
167 + <SelectControl
168 + __nextHasNoMarginBottom
169 + __next40pxDefaultSize
170 + id="tables-export-format"
171 + name="export[format]"
172 + value={ screenData.exportFormat }
173 + label={ __( 'Export Format', 'tablepress' ) }
174 + hideLabelFromVision={ true }
175 + onChange={ ( exportFormat ) => updateScreenData( { exportFormat } ) }
176 + options={ exportFormatsSelectOptions }
177 + />
178 + </HStack>
171 179 </td>
172 180 </tr>
173 181 <tr>
174 182 <th className="column-1" scope="row">
@@ -176,25 +184,29 @@
176 184 { __( 'CSV Delimiter', 'tablepress' ) }:
177 185 </label>
178 186 </th>
179 187 <td className="column-2">
180 - <select
181 - id="tables-export-csv-delimiter"
182 - name="export[csv_delimiter]"
183 - disabled={ 'csv' !== screenData.exportFormat }
184 - value={ screenData.csvDelimiter }
185 - onChange={ ( event ) => updateScreenData( { csvDelimiter: event.target.value } ) }
188 + <HStack
189 + alignment="left"
186 190 >
187 - { csvDelimitersSelectOptions }
188 - </select>
189 - { 'csv' !== screenData.exportFormat &&
190 - <>
191 - { ' ' }
192 - <span className="description">
191 + <SelectControl
192 + __nextHasNoMarginBottom
193 + __next40pxDefaultSize
194 + id="tables-export-csv-delimiter"
195 + name="export[csv_delimiter]"
196 + value={ screenData.csvDelimiter }
197 + label={ __( 'CSV Delimiter', 'tablepress' ) }
198 + hideLabelFromVision={ true }
199 + onChange={ ( csvDelimiter ) => updateScreenData( { csvDelimiter } ) }
200 + options={ csvDelimitersSelectOptions }
201 + disabled={ 'csv' !== screenData.exportFormat }
202 + />
203 + { 'csv' !== screenData.exportFormat &&
204 + <span>
193 205 { __( '(Only needed for CSV export.)', 'tablepress' ) }
194 206 </span>
195 - </>
196 - }
207 + }
208 + </HStack>
197 209 </td>
198 210 </tr>
199 211 <tr className="bottom-border">
200 212 <th className="column-1" scope="row">
@@ -201,25 +213,24 @@
201 213 { __( 'ZIP file', 'tablepress' ) }:
202 214 </th>
203 215 <td className="column-2">
204 216 { tp.export.zipSupportAvailable &&
205 - <label htmlFor="tables-export-zip-file">
206 - <input
207 - type="checkbox"
208 - id="tables-export-zip-file"
217 + <HStack
218 + alignment="left"
219 + >
220 + <CheckboxControl
221 + __nextHasNoMarginBottom
222 + label={ __( 'Create a ZIP archive.', 'tablepress' ) }
209 223 checked={ screenData.createZipFile || zipFileRequired }
210 224 disabled={ zipFileRequired }
211 - onChange={ () => updateScreenData( { createZipFile: ! screenData.createZipFile } ) }
212 - /> { __( 'Create a ZIP archive.', 'tablepress' ) }
225 + onChange={ ( createZipFile ) => updateScreenData( { createZipFile } ) }
226 + />
213 227 { zipFileRequired &&
214 - <>
215 - { ' ' }
216 - <span className="description">
217 - { __( '(Mandatory if more than one table is selected.)', 'tablepress' ) }
218 - </span>
219 - </>
228 + <span>
229 + { __( '(Mandatory if more than one table is selected.)', 'tablepress' ) }
230 + </span>
220 231 }
221 - </label>
232 + </HStack>
222 233 }
223 234 { ! tp.export.zipSupportAvailable &&
224 235 __( 'Note: Support for ZIP file creation seems not to be available on this server.', 'tablepress' )
225 236 }
@@ -228,22 +239,24 @@
228 239 <tr className="top-border">
229 240 <td className="column-1"></td>
230 241 <td className="column-2">
231 242 <input
243 + // Send the list of tables to be exported as a string and not an array, to reduce potential issues with large arrays.
232 244 type="hidden"
233 245 name="export[tables_list]"
234 246 value={ screenData.selectedTables.join() }
235 247 />
236 248 <input
249 + // Send the ZIP file attribute as a hidden field, as disabled checkboxes are not sent in HTTP POST requests.
237 250 type="hidden"
238 251 name="export[zip_file]"
239 252 value={ screenData.createZipFile || zipFileRequired }
240 253 />
241 - <input
254 + <Button
255 + variant="primary"
242 256 type="submit"
243 - value={ __( 'Download Export File', 'tablepress' ) }
244 - className="button button-primary button-large"
245 257 disabled={ 0 === screenData.selectedTables.length }
258 + text={ __( 'Download Export File', 'tablepress' ) }
246 259 />
247 260 </td>
248 261 </tr>
249 262 </tbody>