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/import/screen.jsx +91 -126 2.4.3 → 3.4 View file →
@@ -6,16 +6,21 @@
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 13 import { useEffect, useRef, useState } from 'react';
16 14 import {
15 + Button,
16 + __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
17 17 Icon,
18 + RadioControl,
19 + ComboboxControl,
20 + Disabled,
21 + TextareaControl,
22 + TextControl,
18 23 } from '@wordpress/components';
19 24 import { info } from '@wordpress/icons';
20 25 import { __, _n, _x, sprintf } from '@wordpress/i18n';
21 26
@@ -44,20 +49,26 @@
44 49 if ( ! tp.import.showImportSourceServer ) {
45 50 delete importSources.server;
46 51 }
47 52
53 +const importSourcesRadioOptions = Object.entries( importSources ).map( ( [ importSource, importSourceData ] ) => ( { value: importSource, label: importSourceData.label } ) );
54 +
48 55 // Number of tables.
49 56 const tablesCount = Object.keys( tp.import.tables ).length;
50 57
51 -// The <option> entries for the dropdown do not depend on the state, so they can be created once.
52 58 const tablesSelectOptions = Object.entries( tp.import.tables ).map( ( [ tableId, tableName ] ) => {
53 59 if ( '' === tableName.trim() ) {
54 60 tableName = __( '(no name)', 'tablepress' );
55 61 }
56 62 const optionText = sprintf( __( 'ID %1$s: %2$s', 'tablepress' ), tableId, tableName );
57 - return <option key={ tableId } value={ tableId }>{ optionText }</option>;
63 + return { value: tableId, label: optionText };
58 64 } );
59 65
66 +// Custom component to conditionally disable its children, used for the ComboboxControl.
67 +const ConditionalDisabled = ( { condition, children } ) => (
68 + condition ? ( <Disabled>{ children }</Disabled> ) : children
69 +);
70 +
60 71 /**
61 72 * Returns the "Import Screen" component's JSX markup.
62 73 *
63 74 * @return {Object} Import Screen component.
@@ -76,46 +87,29 @@
76 87
77 88 /**
78 89 * Handles screen data state changes.
79 90 *
80 - * @param {Object} updatedData Data in the screen data state that should be updated.
91 + * @param {Object} updatedScreenData Data in the screen data state that should be updated.
81 92 */
82 - const updateScreenData = ( updatedData ) => {
83 - const newScreenData = {
84 - ...screenData,
93 + const updateScreenData = ( updatedScreenData ) => {
94 + setScreenData( ( currentScreenData ) => ( {
95 + ...currentScreenData,
85 96 validationHighlighting: false, // Reset with every UI state change.
86 - ...updatedData,
87 - };
88 - setScreenData( newScreenData );
97 + ...updatedScreenData,
98 + } ) );
89 99 };
90 100
91 101 // References to DOM elements.
92 102 const importServerInput = useRef( null );
93 - const appendReplaceDropdown = useRef( null );
94 103 const fileUploadDropzone = useRef( null );
95 104
96 - // Initialize the jSuites dropdown when the component is mounted.
97 - useEffect( () => {
98 - jSuites.dropdown( appendReplaceDropdown.current, {
99 - autocomplete: true,
100 - placeholder: __( '— Select or type —', 'tablepress' ),
101 - onchange: ( element, index, oldValue, newValue ) => {
102 - // Directly update the state with an updater function, as the state is otherwise reset.
103 - setScreenData( ( newScreenData ) => ( {
104 - ...newScreenData,
105 - validationHighlighting: false,
106 - importExistingTable: newValue,
107 - } ) );
108 - },
109 - } );
110 - }, [] );
111 -
112 105 // Update the validation highlighting (using APIs and DOM elements outside of the React components) when the state changes.
113 106 useEffect( () => {
114 107 document.getElementById( 'tablepress_import-import-form' ).classList.toggle( 'no-validation-highlighting', ! screenData.validationHighlighting );
115 108 if ( ! screenData.validationHighlighting ) {
116 109 importServerInput.current?.setCustomValidity( '' );
117 - appendReplaceDropdown.current.previousElementSibling.querySelector( '.jdropdown-header' )?.setCustomValidity( '' );
110 + // We need to use this dynamically generated ID by the ComboboxControl component. It does not (yet?) support a static ID or a ref.
111 + document.getElementById( 'components-form-token-input-combobox-control-1' )?.setCustomValidity( '' );
118 112 }
119 113 }, [ screenData.validationHighlighting ] );
120 114
121 115 // Determine calculated state variables to avoid repeating calculations.
@@ -134,39 +128,24 @@
134 128 || 'add' === screenData.importType
135 129 || fileUploadMultipleFilesChosen
136 130 );
137 131
138 - // Disable the artificial dropdown (not inserted by React) via a class, as it can not use :disabled.
139 - useEffect( () => {
140 - appendReplaceDropdown.current.previousElementSibling.classList.toggle( 'disabled', appendReplaceDropdownDisabled );
141 - }, [ appendReplaceDropdownDisabled ] );
142 -
143 132 return (
144 133 <table className="tablepress-postbox-table fixed">
145 134 <tbody>
146 135 <tr>
147 - <th className="column-1" scope="row" id="import-source-header">
136 + <th className="column-1" scope="row">
148 137 { __( 'Import Source', 'tablepress' ) }:
149 138 </th>
150 139 <td className="column-2">
151 - {
152 - Object.entries( importSources ) .map( ( [ importSource, importSourceData ] ) => (
153 - <label
154 - key={ importSource }
155 - htmlFor={ `tables-import-source-${ importSource }` }
156 - >
157 - <input
158 - name="import[source]"
159 - id={ `tables-import-source-${ importSource }` }
160 - type="radio"
161 - aria-labelledby="import-source-header"
162 - value={ importSource }
163 - checked={ importSource === screenData.importSource }
164 - onChange={ ( event ) => updateScreenData( { importSource: event.target.value } ) }
165 - /> { importSourceData.label }
166 - </label>
167 - ) )
168 - }
140 + <RadioControl
141 + name="import[source]"
142 + label={ __( 'Import Source', 'tablepress' ) }
143 + hideLabelFromVision={ true }
144 + selected={ screenData.importSource }
145 + onChange={ ( importSource ) => updateScreenData( { importSource } ) }
146 + options={ importSourcesRadioOptions }
147 + />
169 148 </td>
170 149 </tr>
171 150 <tr className="top-border bottom-border">
172 151 <th className="column-1 top-align" scope="row">
@@ -213,117 +192,97 @@
213 192 }
214 193 </div>
215 194 </div>
216 195 { tp.import.showImportSourceUrl && 'url' === screenData.importSource &&
217 - <input
196 + <TextControl
197 + __nextHasNoMarginBottom
198 + __next40pxDefaultSize
218 199 type="url"
219 200 name="import[url]"
220 201 id="tables-import-url"
221 - className="large-text code"
202 + className="code"
222 203 required={ true }
223 204 value={ screenData.importUrl }
224 - onChange={ ( event ) => updateScreenData( { importUrl: event.target.value } ) }
205 + onChange={ ( importUrl ) => updateScreenData( { importUrl } ) }
225 206 />
226 207 }
227 208 { tp.import.showImportSourceServer && 'server' === screenData.importSource &&
228 - <input
209 + <TextControl
210 + __nextHasNoMarginBottom
211 + __next40pxDefaultSize
229 212 ref={ importServerInput }
230 - type="text"
231 213 name="import[server]"
232 214 id="tables-import-server"
233 - className="large-text code"
215 + className="code"
234 216 required={ true }
235 217 value={ screenData.importServer }
236 - onChange={ ( event ) => updateScreenData( { importServer: event.target.value } ) }
218 + onChange={ ( importServer ) => updateScreenData( { importServer } ) }
237 219 />
238 220 }
239 221 { 'form-field' === screenData.importSource &&
240 - <textarea
222 + <TextareaControl
223 + __nextHasNoMarginBottom
241 224 name="import[form-field]"
242 225 id="tables-import-form-field"
243 226 rows="15"
244 227 cols="40"
245 - className="large-text code"
228 + className="code"
246 229 required={ true }
247 230 value={ screenData.importFormField }
248 - onChange={ ( event ) => updateScreenData( { importFormField: event.target.value } ) }
231 + onChange={ ( importFormField ) => updateScreenData( { importFormField } ) }
249 232 />
250 233 }
251 234 { 'form-field' !== screenData.importSource &&
252 - <p className="info-text" style={ {
253 - marginTop: '0.5em',
254 - } }>
235 + <HStack
236 + alignment="left"
237 + >
255 238 <Icon icon={ info } />
256 239 <span>
257 240 { __( 'You can also import multiple tables by placing them in a ZIP file.', 'tablepress' ) }
258 241 </span>
259 - </p>
242 + </HStack>
260 243 }
261 244 </td>
262 245 </tr>
263 246 <tr className="top-border">
264 - <th className="column-1" scope="row" id="import-type-header">
247 + <th className="column-1" scope="row">
265 248 { __( 'Add, Replace, or Append?', 'tablepress' ) }:
266 249 </th>
267 250 <td className="column-2">
268 - <label htmlFor="tables-import-type-add">
269 - <input
270 - name="import[type]"
271 - id="tables-import-type-add"
272 - type="radio"
273 - aria-labelledby="import-type-header"
274 - value="add"
275 - checked={ 'add' === screenData.importType || 0 === tablesCount }
276 - onChange={ ( event ) => updateScreenData( { importType: event.target.value } ) }
277 - /> { __( 'Add as new table', 'tablepress' ) }
278 - </label>
279 - <label htmlFor="tables-import-type-replace">
280 - <input
281 - name="import[type]"
282 - id="tables-import-type-replace"
283 - type="radio"
284 - aria-labelledby="import-type-header"
285 - value="replace"
286 - disabled={ 0 === tablesCount }
287 - checked={ 'replace' === screenData.importType }
288 - onChange={ ( event ) => updateScreenData( { importType: event.target.value } ) }
289 - /> { __( 'Replace existing table', 'tablepress' ) }
290 - </label>
291 - <label htmlFor="tables-import-type-append">
292 - <input
293 - name="import[type]"
294 - id="tables-import-type-append"
295 - type="radio"
296 - aria-labelledby="import-type-header"
297 - value="append"
298 - disabled={ 0 === tablesCount }
299 - checked={ 'append' === screenData.importType }
300 - onChange={ ( event ) => updateScreenData( { importType: event.target.value } ) }
301 - /> { __( 'Append rows to existing table', 'tablepress' ) }
302 - </label>
251 + <RadioControl
252 + name="import[type]"
253 + label={ __( 'Import Type', 'tablepress' ) }
254 + hideLabelFromVision={ true }
255 + selected={ 0 === tablesCount ? 'add' : screenData.importType } // Always select "Add" if there are no tables.
256 + onChange={ ( importType ) => updateScreenData( { importType } ) }
257 + options={ [
258 + { value: 'add', label: __( 'Add as new table', 'tablepress' ) },
259 + { value: 'replace', label: __( 'Replace existing table', 'tablepress' ), disabled: 0 === tablesCount },
260 + { value: 'append', label: __( 'Append rows to existing table', 'tablepress' ), disabled: 0 === tablesCount },
261 + ] }
262 + />
303 263 </td>
304 264 </tr>
305 265 <tr className="top-border bottom-border">
306 - <th className="column-1" scope="row">
307 - <label htmlFor="tables-import-existing-table">
308 - { __( 'Table to replace or append to', 'tablepress' ) }:
309 - </label>
266 + <th className="column-1 top-align" scope="row">
267 + <label htmlFor="tables-import-existing-table">{ __( 'Table to replace or append to', 'tablepress' ) }:</label>
310 268 </th>
311 269 <td className="column-2">
312 - <select
313 - ref={ appendReplaceDropdown }
314 - id="tables-import-existing-table"
315 - name="import[existing_table]"
316 - disabled={ appendReplaceDropdownDisabled }
317 - value={ screenData.importExistingTable }
270 + <ConditionalDisabled
271 + condition={ appendReplaceDropdownDisabled }
318 272 >
319 - <option value="">
320 - {
321 - ' ' // Use a space as an empty string will be printed as `&nbsp;` by jSuites.
322 - }
323 - </option>
324 - { tablesSelectOptions }
325 - </select>
273 + <ComboboxControl
274 + __nextHasNoMarginBottom
275 + __next40pxDefaultSize
276 + id="tables-import-existing-table"
277 + label={ __( 'Table to replace or append to', 'tablepress' ) }
278 + hideLabelFromVision={ true }
279 + placeholder={ __( '— Select or type —', 'tablepress' ) }
280 + value={ screenData.importExistingTable }
281 + options={ tablesSelectOptions }
282 + onChange={ ( importExistingTable ) => updateScreenData( { importExistingTable } ) }
283 + />
284 + </ConditionalDisabled>
326 285 </td>
327 286 </tr>
328 287 <tr className="top-border">
329 288 <td className="column-1"></td>
@@ -328,17 +287,23 @@
328 287 <tr className="top-border">
329 288 <td className="column-1"></td>
330 289 <td className="column-2">
331 290 <input
291 + // Send the legacy import flag to the server, so that it can handle the import accordingly.
332 292 type="hidden"
333 293 name="import[legacy_import]"
334 294 value={ tp.import.legacyImport }
335 295 />
336 296 <input
297 + // Send the Table to be replaced/appended to the server, if a table was selected. The ComboboxControl is not an actual form element with a name.
298 + type="hidden"
299 + name="import[existing_table]"
300 + value={ screenData.importExistingTable ?? '' }
301 + />
302 + <Button
303 + variant="primary"
337 304 type="submit"
338 - value={ _x( 'Import', 'button', 'tablepress' ) }
339 - className="button button-primary button-large"
340 - id="import-submit-button"
305 + text={ _x( 'Import', 'button', 'tablepress' ) }
341 306 onClick={ () => {
342 307 // Show validation :invalid CSS pseudo-selector highlighting.
343 308 updateScreenData( { validationHighlighting: true } );
344 309
@@ -347,11 +312,11 @@
347 312 importServerInput.current.setCustomValidity( __( 'You must specify a path to a file on the server.', 'tablepress' ) );
348 313 }
349 314
350 315 // If the table selection dropdown for replace or append is enabled, a table must be selected.
351 - if ( ! appendReplaceDropdownDisabled && '' === screenData.importExistingTable ) {
352 - // Use the jSuites dropdown input field, as the actual <select> is hidden.
353 - appendReplaceDropdown.current.previousElementSibling.querySelector( '.jdropdown-header' ).setCustomValidity( __( 'You must select a table.', 'tablepress' ) );
316 + if ( ! appendReplaceDropdownDisabled && ! screenData.importExistingTable ) {
317 + // We need to use this dynamically generated ID by the ComboboxControl component. It does not (yet?) support a static ID or a ref.
318 + document.getElementById( 'components-form-token-input-combobox-control-1' )?.setCustomValidity( __( 'You must select a table.', 'tablepress' ) );
354 319 }
355 320 } }
356 321 />
357 322 </td>