PluginProbe
CryptX / trunk
CryptX vtrunk
4.2.0 4.1.1 trunk 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.9 2.0 2.1 2.2 2.3 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 All 92 releases
cryptx / src / components / Field.js

Field.js in CryptX trunk, at src/components/Field.js

256 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Renders one option from its schema entry.
3 *
4 * Every branch here maps a type from SettingsSchema.php to a control. The help
5 * text is never optional -- it comes from the schema, and the schema requires
6 * one for every field.
7 */
8
9 import { __ } from '@wordpress/i18n';
10 import { useState } from '@wordpress/element';
11 import {
12 BaseControl,
13 Button,
14 RadioControl,
15 SelectControl,
16 TextControl,
17 TextareaControl,
18 ToggleControl,
19 __experimentalVStack as VStack,
20 } from '@wordpress/components';
21
22 /**
23 * Whether a field's condition is met by the current values.
24 *
25 * @param {Object} field The schema entry.
26 * @param {Object} values Current values.
27 * @return {boolean} True when the field should be shown.
28 */
29 export function isVisible( field, values ) {
30 if ( ! field.depends ) {
31 return true;
32 }
33
34 return Object.entries( field.depends ).every(
35 // Loose comparison: a choice value of 1 arrives as a number from the
36 // schema but may sit in the form state as the string "1".
37 // eslint-disable-next-line eqeqeq
38 ( [ key, expected ] ) => values[ key ] == expected
39 );
40 }
41
42 /**
43 * Opens the media library and hands back the chosen attachment.
44 *
45 * @param {Function} onSelect Receives the attachment id and its preview url.
46 */
47 function openMediaLibrary( onSelect ) {
48 if ( ! window.wp?.media ) {
49 return;
50 }
51
52 const frame = window.wp.media( {
53 title: __( 'Choose an image', 'cryptx' ),
54 multiple: false,
55 library: { type: 'image' },
56 } );
57
58 frame.on( 'select', () => {
59 const attachment = frame.state().get( 'selection' ).first().toJSON();
60
61 onSelect( attachment.id, attachment.url );
62 } );
63
64 frame.open();
65 }
66
67 export default function Field( { field, value, onChange } ) {
68 const [ mediaUrl, setMediaUrl ] = useState( null );
69 const id = `cryptx-field-${ field.key }`;
70
71 switch ( field.type ) {
72 case 'boolean':
73 return (
74 <ToggleControl
75 __nextHasNoMarginBottom
76 label={ field.label }
77 help={ field.help }
78 checked={ !! Number( value ) }
79 onChange={ ( checked ) =>
80 onChange( field.key, checked ? 1 : 0 )
81 }
82 />
83 );
84
85 case 'choice': {
86 const options = field.choices.map( ( choice ) => ( {
87 label: choice.label,
88 value: String( choice.value ),
89 } ) );
90
91 // Radio buttons show every option and its consequence at a glance,
92 // which matters for the security choices. Beyond four they turn
93 // into a wall, so those become a select.
94 if ( options.length <= 4 ) {
95 return (
96 <RadioControl
97 label={ field.label }
98 help={ field.help }
99 selected={ String( value ) }
100 options={ options }
101 onChange={ ( next ) => onChange( field.key, next ) }
102 />
103 );
104 }
105
106 return (
107 <SelectControl
108 __nextHasNoMarginBottom
109 __next40pxDefaultSize
110 label={ field.label }
111 help={ field.help }
112 value={ String( value ) }
113 options={ options }
114 onChange={ ( next ) => onChange( field.key, next ) }
115 />
116 );
117 }
118
119 case 'integer':
120 return (
121 <TextControl
122 __nextHasNoMarginBottom
123 __next40pxDefaultSize
124 type="number"
125 min={ field.min }
126 max={ field.max }
127 label={ field.label }
128 help={ field.help }
129 value={ value }
130 onChange={ ( next ) => onChange( field.key, next ) }
131 />
132 );
133
134 case 'idlist':
135 case 'addresslist':
136 return (
137 <TextareaControl
138 __nextHasNoMarginBottom
139 label={ field.label }
140 help={ field.help }
141 value={ value }
142 rows={ 3 }
143 onChange={ ( next ) => onChange( field.key, next ) }
144 />
145 );
146
147 case 'color':
148 return (
149 <BaseControl
150 __nextHasNoMarginBottom
151 id={ id }
152 label={ field.label }
153 help={ field.help }
154 >
155 <div className="cryptx-color">
156 { /* A native colour input works on every device,
157 including the phone's own colour picker, and needs
158 no extra bundle. */ }
159 <input
160 id={ id }
161 type="color"
162 value={ value || '#000000' }
163 onChange={ ( event ) =>
164 onChange( field.key, event.target.value )
165 }
166 />
167 <TextControl
168 __nextHasNoMarginBottom
169 __next40pxDefaultSize
170 label={ __( 'Hex value', 'cryptx' ) }
171 hideLabelFromVision
172 value={ value }
173 onChange={ ( next ) => onChange( field.key, next ) }
174 />
175 </div>
176 </BaseControl>
177 );
178
179 case 'media':
180 return (
181 <BaseControl
182 __nextHasNoMarginBottom
183 id={ id }
184 label={ field.label }
185 help={ field.help }
186 >
187 <VStack spacing={ 2 } className="cryptx-media">
188 { mediaUrl && (
189 <img
190 src={ mediaUrl }
191 alt=""
192 className="cryptx-media__preview"
193 />
194 ) }
195 <div className="cryptx-media__buttons">
196 <Button
197 variant="secondary"
198 __next40pxDefaultSize
199 onClick={ () =>
200 openMediaLibrary( ( attachmentId, url ) => {
201 onChange( field.key, attachmentId );
202 setMediaUrl( url );
203 } )
204 }
205 >
206 { Number( value )
207 ? __( 'Replace image', 'cryptx' )
208 : __( 'Choose image', 'cryptx' ) }
209 </Button>
210 { !! Number( value ) && (
211 <Button
212 variant="tertiary"
213 isDestructive
214 __next40pxDefaultSize
215 onClick={ () => {
216 onChange( field.key, 0 );
217 setMediaUrl( null );
218 } }
219 >
220 { __( 'Remove', 'cryptx' ) }
221 </Button>
222 ) }
223 </div>
224 </VStack>
225 </BaseControl>
226 );
227
228 case 'url':
229 return (
230 <TextControl
231 __nextHasNoMarginBottom
232 __next40pxDefaultSize
233 type="url"
234 label={ field.label }
235 help={ field.help }
236 value={ value }
237 onChange={ ( next ) => onChange( field.key, next ) }
238 />
239 );
240
241 case 'htmlid':
242 case 'string':
243 default:
244 return (
245 <TextControl
246 __nextHasNoMarginBottom
247 __next40pxDefaultSize
248 label={ field.label }
249 help={ field.help }
250 value={ value }
251 onChange={ ( next ) => onChange( field.key, next ) }
252 />
253 );
254 }
255 }
256