@@ -7,7 +7,15 @@ import { Map, List } from 'immutable';
7
7
import { once } from 'lodash' ;
8
8
import uuid from 'uuid/v4' ;
9
9
import { oneLine } from 'common-tags' ;
10
- import { lengths , components , buttons , borders , effects , shadows } from 'netlify-cms-ui-default' ;
10
+ import {
11
+ lengths ,
12
+ components ,
13
+ buttons ,
14
+ borders ,
15
+ effects ,
16
+ shadows ,
17
+ IconButton ,
18
+ } from 'netlify-cms-ui-default' ;
11
19
import { basename } from 'netlify-cms-lib-util' ;
12
20
import { SortableContainer , SortableElement } from 'react-sortable-hoc' ;
13
21
import { arrayMoveImmutable as arrayMove } from 'array-move' ;
@@ -28,6 +36,15 @@ const ImageWrapper = styled.div`
28
36
cursor: ${ props => ( props . sortable ? 'pointer' : 'auto' ) } ;
29
37
` ;
30
38
39
+ const SortableImageButtonsWrapper = styled . div `
40
+ display: flex;
41
+ justify-content: center;
42
+ column-gap: 10px;
43
+ margin-right: 20px;
44
+ margin-top: -10px;
45
+ margin-bottom: 10px;
46
+ ` ;
47
+
31
48
const StyledImage = styled . img `
32
49
width: 100%;
33
50
height: 100%;
@@ -38,35 +55,55 @@ function Image(props) {
38
55
return < StyledImage role = "presentation" { ...props } /> ;
39
56
}
40
57
41
- const SortableImage = SortableElement ( ( { itemValue , getAsset , field } ) => {
58
+ function SortableImageButtons ( { onRemove , onReplace } ) {
42
59
return (
43
- < ImageWrapper sortable >
44
- < Image src = { getAsset ( itemValue , field ) || '' } />
45
- </ ImageWrapper >
60
+ < SortableImageButtonsWrapper >
61
+ < IconButton size = "small" type = "media" onClick = { onReplace } > </ IconButton >
62
+ < IconButton size = "small" type = "close" onClick = { onRemove } > </ IconButton >
63
+ </ SortableImageButtonsWrapper >
46
64
) ;
47
- } ) ;
65
+ }
48
66
49
- const SortableMultiImageWrapper = SortableContainer ( ( { items , getAsset, field } ) => {
67
+ const SortableImage = SortableElement ( ( { itemValue , getAsset, field, onRemove , onReplace } ) => {
50
68
return (
51
- < div
52
- css = { css `
53
- display: flex;
54
- flex-wrap: wrap;
55
- ` }
56
- >
57
- { items . map ( ( itemValue , index ) => (
58
- < SortableImage
59
- key = { `item-${ itemValue } ` }
60
- index = { index }
61
- itemValue = { itemValue }
62
- getAsset = { getAsset }
63
- field = { field }
64
- />
65
- ) ) }
69
+ < div >
70
+ < ImageWrapper sortable >
71
+ < Image src = { getAsset ( itemValue , field ) || '' } />
72
+ </ ImageWrapper >
73
+ < SortableImageButtons
74
+ item = { itemValue }
75
+ onRemove = { onRemove }
76
+ onReplace = { onReplace }
77
+ > </ SortableImageButtons >
66
78
</ div >
67
79
) ;
68
80
} ) ;
69
81
82
+ const SortableMultiImageWrapper = SortableContainer (
83
+ ( { items, getAsset, field, onRemoveOne, onReplaceOne } ) => {
84
+ return (
85
+ < div
86
+ css = { css `
87
+ display: flex;
88
+ flex-wrap: wrap;
89
+ ` }
90
+ >
91
+ { items . map ( ( itemValue , index ) => (
92
+ < SortableImage
93
+ key = { `item-${ itemValue } ` }
94
+ index = { index }
95
+ itemValue = { itemValue }
96
+ getAsset = { getAsset }
97
+ field = { field }
98
+ onRemove = { onRemoveOne ( index ) }
99
+ onReplace = { onReplaceOne ( index ) }
100
+ />
101
+ ) ) }
102
+ </ div >
103
+ ) ;
104
+ } ,
105
+ ) ;
106
+
70
107
const FileLink = styled . a `
71
108
margin-bottom: 20px;
72
109
font-weight: normal;
@@ -102,6 +139,22 @@ function isMultiple(value) {
102
139
return Array . isArray ( value ) || List . isList ( value ) ;
103
140
}
104
141
142
+ function sizeOfValue ( value ) {
143
+ if ( Array . isArray ( value ) ) {
144
+ return value . length ;
145
+ }
146
+
147
+ if ( List . isList ( value ) ) {
148
+ return value . size ;
149
+ }
150
+
151
+ return value ? 1 : 0 ;
152
+ }
153
+
154
+ function valueListToArray ( value ) {
155
+ return List . isList ( value ) ? value . toArray ( ) : value ;
156
+ }
157
+
105
158
const warnDeprecatedOptions = once ( field =>
106
159
console . warn ( oneLine `
107
160
Netlify CMS config: ${ field . get ( 'name' ) } field: property "options" has been deprecated for the
@@ -178,26 +231,13 @@ export default function withFileControl({ forImage } = {}) {
178
231
handleChange = e => {
179
232
const { field, onOpenMediaLibrary, value } = this . props ;
180
233
e . preventDefault ( ) ;
181
- let mediaLibraryFieldOptions ;
182
-
183
- /**
184
- * `options` hash as a general field property is deprecated, only used
185
- * when external media libraries were first introduced. Not to be
186
- * confused with `options` for the select widget, which serves a different
187
- * purpose.
188
- */
189
- if ( field . hasIn ( [ 'options' , 'media_library' ] ) ) {
190
- warnDeprecatedOptions ( field ) ;
191
- mediaLibraryFieldOptions = field . getIn ( [ 'options' , 'media_library' ] , Map ( ) ) ;
192
- } else {
193
- mediaLibraryFieldOptions = field . get ( 'media_library' , Map ( ) ) ;
194
- }
234
+ const mediaLibraryFieldOptions = this . getMediaLibraryFieldOptions ( ) ;
195
235
196
236
return onOpenMediaLibrary ( {
197
237
controlID : this . controlID ,
198
238
forImage,
199
239
privateUpload : field . get ( 'private' ) ,
200
- value,
240
+ value : valueListToArray ( value ) ,
201
241
allowMultiple : ! ! mediaLibraryFieldOptions . get ( 'allow_multiple' , true ) ,
202
242
config : mediaLibraryFieldOptions . get ( 'config' ) ,
203
243
field,
@@ -218,6 +258,47 @@ export default function withFileControl({ forImage } = {}) {
218
258
return this . props . onChange ( '' ) ;
219
259
} ;
220
260
261
+ onRemoveOne = index => ( ) => {
262
+ const { value } = this . props ;
263
+ value . splice ( index , 1 ) ;
264
+ return this . props . onChange ( sizeOfValue ( value ) > 0 ? [ ...value ] : null ) ;
265
+ } ;
266
+
267
+ onReplaceOne = index => ( ) => {
268
+ const { field, onOpenMediaLibrary, value } = this . props ;
269
+ const mediaLibraryFieldOptions = this . getMediaLibraryFieldOptions ( ) ;
270
+
271
+ return onOpenMediaLibrary ( {
272
+ controlID : this . controlID ,
273
+ forImage,
274
+ privateUpload : field . get ( 'private' ) ,
275
+ value : valueListToArray ( value ) ,
276
+ replaceIndex : index ,
277
+ allowMultiple : false ,
278
+ config : mediaLibraryFieldOptions . get ( 'config' ) ,
279
+ field,
280
+ } ) ;
281
+ } ;
282
+
283
+ getMediaLibraryFieldOptions = ( ) => {
284
+ const { field } = this . props ;
285
+
286
+ if ( field . hasIn ( [ 'options' , 'media_library' ] ) ) {
287
+ warnDeprecatedOptions ( field ) ;
288
+ return field . getIn ( [ 'options' , 'media_library' ] , Map ( ) ) ;
289
+ }
290
+
291
+ return field . get ( 'media_library' , Map ( ) ) ;
292
+ } ;
293
+
294
+ allowsMultiple = ( ) => {
295
+ const mediaLibraryFieldOptions = this . getMediaLibraryFieldOptions ( ) ;
296
+ return (
297
+ mediaLibraryFieldOptions . get ( 'config' , false ) &&
298
+ mediaLibraryFieldOptions . get ( 'config' ) . get ( 'multiple' , false )
299
+ ) ;
300
+ } ;
301
+
221
302
onSortEnd = ( { oldIndex, newIndex } ) => {
222
303
const { value } = this . props ;
223
304
const newValue = arrayMove ( value , oldIndex , newIndex ) ;
@@ -274,6 +355,9 @@ export default function withFileControl({ forImage } = {}) {
274
355
< SortableMultiImageWrapper
275
356
items = { value }
276
357
onSortEnd = { this . onSortEnd }
358
+ onRemoveOne = { this . onRemoveOne }
359
+ onReplaceOne = { this . onReplaceOne }
360
+ distance = { 4 }
277
361
getAsset = { getAsset }
278
362
field = { field }
279
363
axis = "xy"
@@ -292,21 +376,26 @@ export default function withFileControl({ forImage } = {}) {
292
376
293
377
renderSelection = subject => {
294
378
const { t, field } = this . props ;
379
+ const allowsMultiple = this . allowsMultiple ( ) ;
295
380
return (
296
381
< div >
297
382
{ forImage ? this . renderImages ( ) : null }
298
383
< div >
299
384
{ forImage ? null : this . renderFileLinks ( ) }
300
385
< FileWidgetButton onClick = { this . handleChange } >
301
- { t ( `editor.editorWidgets.${ subject } .chooseDifferent` ) }
386
+ { t (
387
+ `editor.editorWidgets.${ subject } .${
388
+ this . allowsMultiple ( ) ? 'addMore' : 'chooseDifferent'
389
+ } `,
390
+ ) }
302
391
</ FileWidgetButton >
303
- { field . get ( 'choose_url' , true ) ? (
392
+ { field . get ( 'choose_url' , true ) && ! this . allowsMultiple ( ) ? (
304
393
< FileWidgetButton onClick = { this . handleUrl ( subject ) } >
305
394
{ t ( `editor.editorWidgets.${ subject } .replaceUrl` ) }
306
395
</ FileWidgetButton >
307
396
) : null }
308
397
< FileWidgetButtonRemove onClick = { this . handleRemove } >
309
- { t ( `editor.editorWidgets.${ subject } .remove` ) }
398
+ { t ( `editor.editorWidgets.${ subject } .remove${ allowsMultiple ? 'All' : '' } ` ) }
310
399
</ FileWidgetButtonRemove >
311
400
</ div >
312
401
</ div >
@@ -318,7 +407,7 @@ export default function withFileControl({ forImage } = {}) {
318
407
return (
319
408
< >
320
409
< FileWidgetButton onClick = { this . handleChange } >
321
- { t ( `editor.editorWidgets.${ subject } .choose` ) }
410
+ { t ( `editor.editorWidgets.${ subject } .choose${ this . allowsMultiple ( ) ? 'Multiple' : '' } ` ) }
322
411
</ FileWidgetButton >
323
412
{ field . get ( 'choose_url' , true ) ? (
324
413
< FileWidgetButton onClick = { this . handleUrl ( subject ) } >
0 commit comments