| 1 |
const StockpackImage = wp.media.View.extend( { |
| 2 |
tagName: 'li', |
| 3 |
className: 'attachment', |
| 4 |
template: wp.template( 'stockpack-attachment' ), |
| 5 |
events: { |
| 6 |
click: 'toggleSelectionHandler', |
| 7 |
keydown: 'toggleSelectionHandler', |
| 8 |
'click .check': 'checkClickHandler', |
| 9 |
'click .download': 'downloadClickHandler', |
| 10 |
}, |
| 11 |
buttons: { |
| 12 |
check: true, |
| 13 |
}, |
| 14 |
|
| 15 |
maybeUpdateGlobalProvider() { |
| 16 |
const provider = this.options.provider; |
| 17 |
const currentProvider = this.model.get( 'provider' ); |
| 18 |
if ( provider !== currentProvider ) { |
| 19 |
this.controller.trigger( 'update_provider', currentProvider ); |
| 20 |
} |
| 21 |
}, |
| 22 |
|
| 23 |
initialize() { |
| 24 |
const selection = this.options.selection; |
| 25 |
|
| 26 |
this.listenTo( this.model, 'change', this.render ); |
| 27 |
|
| 28 |
// Update the selection. |
| 29 |
this.listenTo( this.model, 'add', this.select ); |
| 30 |
this.listenTo( this.model, 'remove', this.deselect ); |
| 31 |
if ( selection ) { |
| 32 |
selection.on( 'reset', this.updateSelect, this ); |
| 33 |
// Update the model's details view. |
| 34 |
this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); |
| 35 |
this.details( this.model, this.controller.state().get( 'selection' ) ); |
| 36 |
} |
| 37 |
this.maybeUpdateGlobalProvider(); |
| 38 |
}, |
| 39 |
|
| 40 |
/** |
| 41 |
* @return {StockpackImage} Returns itself to allow chaining |
| 42 |
*/ |
| 43 |
dispose() { |
| 44 |
const selection = this.options.selection; |
| 45 |
|
| 46 |
if ( selection ) { |
| 47 |
selection.off( null, null, this ); |
| 48 |
} |
| 49 |
/** |
| 50 |
* call 'dispose' directly on the parent class |
| 51 |
*/ |
| 52 |
wp.media.View.prototype.dispose.apply( this, arguments ); |
| 53 |
return this; |
| 54 |
}, |
| 55 |
/** |
| 56 |
* @return {StockpackImage} Returns itself to allow chaining |
| 57 |
*/ |
| 58 |
render() { |
| 59 |
const options = _.defaults( this.model.toJSON(), { |
| 60 |
orientation: 'landscape', |
| 61 |
type: 'image', |
| 62 |
subtype: '', |
| 63 |
width: '', |
| 64 |
height: '', |
| 65 |
caption: '', |
| 66 |
description: '', |
| 67 |
}, this.options ); |
| 68 |
|
| 69 |
options.buttons = this.buttons; |
| 70 |
|
| 71 |
if ( 'image' === options.type ) { |
| 72 |
options.size = this.imageSize(); |
| 73 |
} |
| 74 |
|
| 75 |
this.views.detach(); |
| 76 |
this.$el.html( this.template( options ) ); |
| 77 |
|
| 78 |
// Check if the model is selected. |
| 79 |
this.updateSelect(); |
| 80 |
|
| 81 |
this.views.render(); |
| 82 |
|
| 83 |
return this; |
| 84 |
}, |
| 85 |
|
| 86 |
/** |
| 87 |
* @param {Object} event |
| 88 |
*/ |
| 89 |
toggleSelectionHandler( event ) { |
| 90 |
let method; |
| 91 |
|
| 92 |
// Don't do anything inside inputs and on the attachment check and remove buttons. |
| 93 |
if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName || 'A' === event.target.nodeName ) { |
| 94 |
return; |
| 95 |
} |
| 96 |
|
| 97 |
// Catch arrow events |
| 98 |
if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { |
| 99 |
this.controller.trigger( 'attachment:keydown:arrow', event ); |
| 100 |
return; |
| 101 |
} |
| 102 |
|
| 103 |
// Catch enter and space events |
| 104 |
if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { |
| 105 |
return; |
| 106 |
} |
| 107 |
|
| 108 |
event.preventDefault(); |
| 109 |
|
| 110 |
// In the grid view, bubble up an edit:attachment event to the controller. |
| 111 |
if ( this.controller.isModeActive( 'grid' ) ) { |
| 112 |
if ( this.controller.isModeActive( 'edit' ) ) { |
| 113 |
// Pass the current target to restore focus when closing |
| 114 |
this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); |
| 115 |
return; |
| 116 |
} |
| 117 |
|
| 118 |
if ( this.controller.isModeActive( 'select' ) ) { |
| 119 |
method = 'toggle'; |
| 120 |
} |
| 121 |
} |
| 122 |
|
| 123 |
if ( event.shiftKey ) { |
| 124 |
method = 'between'; |
| 125 |
} else if ( event.ctrlKey || event.metaKey ) { |
| 126 |
method = 'toggle'; |
| 127 |
} |
| 128 |
|
| 129 |
this.toggleSelection( { |
| 130 |
method, |
| 131 |
} ); |
| 132 |
|
| 133 |
this.controller.trigger( 'selection:toggle' ); |
| 134 |
}, |
| 135 |
/** |
| 136 |
* @param {Object} options |
| 137 |
*/ |
| 138 |
toggleSelection( options ) { |
| 139 |
let method = options && options.method, |
| 140 |
models, singleIndex, modelIndex; |
| 141 |
|
| 142 |
const selection = this.options.selection; |
| 143 |
if ( ! selection ) { |
| 144 |
return; |
| 145 |
} |
| 146 |
const collection = this.collection; |
| 147 |
const model = this.model; |
| 148 |
const single = selection.single(); |
| 149 |
method = _.isUndefined( method ) ? selection.multiple : method; |
| 150 |
|
| 151 |
// If the `method` is set to `between`, select all models that |
| 152 |
// exist between the current and the selected model. |
| 153 |
if ( 'between' === method && single && selection.multiple ) { |
| 154 |
// If the models are the same, short-circuit. |
| 155 |
if ( single === model ) { |
| 156 |
return; |
| 157 |
} |
| 158 |
|
| 159 |
singleIndex = collection.indexOf( single ); |
| 160 |
modelIndex = collection.indexOf( this.model ); |
| 161 |
|
| 162 |
if ( singleIndex < modelIndex ) { |
| 163 |
models = collection.models.slice( singleIndex, modelIndex + 1 ); |
| 164 |
} else { |
| 165 |
models = collection.models.slice( modelIndex, singleIndex + 1 ); |
| 166 |
} |
| 167 |
|
| 168 |
selection.add( models ); |
| 169 |
selection.single( model ); |
| 170 |
return; |
| 171 |
|
| 172 |
// If the `method` is set to `toggle`, just flip the selection |
| 173 |
// status, regardless of whether the model is the single model. |
| 174 |
} else if ( 'toggle' === method ) { |
| 175 |
selection[ this.selected() ? 'remove' : 'add' ]( model ); |
| 176 |
selection.single( model ); |
| 177 |
return; |
| 178 |
} else if ( 'add' === method ) { |
| 179 |
selection.add( model ); |
| 180 |
selection.single( model ); |
| 181 |
return; |
| 182 |
} |
| 183 |
|
| 184 |
// Fixes bug that loses focus when selecting a featured image |
| 185 |
if ( ! method ) { |
| 186 |
method = 'add'; |
| 187 |
} |
| 188 |
|
| 189 |
if ( method !== 'add' ) { |
| 190 |
method = 'reset'; |
| 191 |
} |
| 192 |
|
| 193 |
if ( this.selected() ) { |
| 194 |
// If the model is the single model, remove it. |
| 195 |
// If it is not the same as the single model, |
| 196 |
// it now becomes the single model. |
| 197 |
selection[ single === model ? 'remove' : 'single' ]( model ); |
| 198 |
} else { |
| 199 |
// If the model is not selected, run the `method` on the |
| 200 |
// selection. By default, we `reset` the selection, but the |
| 201 |
// `method` can be set to `add` the model to the selection. |
| 202 |
selection[ method ]( model ); |
| 203 |
selection.single( model ); |
| 204 |
} |
| 205 |
}, |
| 206 |
|
| 207 |
updateSelect() { |
| 208 |
this[ this.selected() ? 'select' : 'deselect' ](); |
| 209 |
}, |
| 210 |
/** |
| 211 |
* @return {boolean} selected |
| 212 |
*/ |
| 213 |
selected() { |
| 214 |
const selection = this.options.selection; |
| 215 |
if ( selection ) { |
| 216 |
return !! selection.get( this.model.cid ); |
| 217 |
} |
| 218 |
}, |
| 219 |
/** |
| 220 |
* @param {Backbone.Model} model |
| 221 |
* @param {Backbone.Collection} collection |
| 222 |
*/ |
| 223 |
select( model, collection ) { |
| 224 |
const selection = this.options.selection, |
| 225 |
controller = this.controller; |
| 226 |
|
| 227 |
// Check if a selection exists and if it's the collection provided. |
| 228 |
// If they're not the same collection, bail; we're in another |
| 229 |
// selection's event loop. |
| 230 |
if ( ! selection || ( collection && collection !== selection ) ) { |
| 231 |
return; |
| 232 |
} |
| 233 |
|
| 234 |
// Bail if the model is already selected. |
| 235 |
if ( this.$el.hasClass( 'selected' ) ) { |
| 236 |
// call download function |
| 237 |
return; |
| 238 |
} |
| 239 |
|
| 240 |
// Add 'selected' class to model, set aria-checked to true. |
| 241 |
this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); |
| 242 |
// Make the checkbox tabable, except in media grid (bulk select mode). |
| 243 |
if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { |
| 244 |
this.$( '.check' ).attr( 'tabindex', '0' ); |
| 245 |
} |
| 246 |
}, |
| 247 |
/** |
| 248 |
* @param {Backbone.Model} model |
| 249 |
* @param {Backbone.Collection} collection |
| 250 |
*/ |
| 251 |
deselect( model, collection ) { |
| 252 |
const selection = this.options.selection; |
| 253 |
|
| 254 |
// Check if a selection exists and if it's the collection provided. |
| 255 |
// If they're not the same collection, bail; we're in another |
| 256 |
// selection's event loop. |
| 257 |
if ( ! selection || ( collection && collection !== selection ) ) { |
| 258 |
return; |
| 259 |
} |
| 260 |
this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) |
| 261 |
.find( '.check' ).attr( 'tabindex', '-1' ); |
| 262 |
}, |
| 263 |
/** |
| 264 |
* @param {Backbone.Model} model |
| 265 |
* @param {Backbone.Collection} collection |
| 266 |
*/ |
| 267 |
details( model, collection ) { |
| 268 |
const selection = this.options.selection; |
| 269 |
|
| 270 |
if ( selection !== collection ) { |
| 271 |
return; |
| 272 |
} |
| 273 |
|
| 274 |
const details = selection.single(); |
| 275 |
this.$el.toggleClass( 'details', details === this.model ); |
| 276 |
}, |
| 277 |
/** |
| 278 |
* @param {string} size |
| 279 |
* @return {Object} sizes |
| 280 |
*/ |
| 281 |
imageSize( size ) { |
| 282 |
const sizes = this.model.get( 'sizes' ); |
| 283 |
let matched = false; |
| 284 |
|
| 285 |
size = size || 'medium'; |
| 286 |
|
| 287 |
// Use the provided image size if possible. |
| 288 |
if ( sizes ) { |
| 289 |
if ( sizes[ size ] ) { |
| 290 |
matched = sizes[ size ]; |
| 291 |
} else if ( sizes.large ) { |
| 292 |
matched = sizes.large; |
| 293 |
} else if ( sizes.thumbnail ) { |
| 294 |
matched = sizes.thumbnail; |
| 295 |
} else if ( sizes.full ) { |
| 296 |
matched = sizes.full; |
| 297 |
} |
| 298 |
|
| 299 |
if ( matched ) { |
| 300 |
return _.clone( matched ); |
| 301 |
} |
| 302 |
} |
| 303 |
|
| 304 |
return { |
| 305 |
url: this.model.get( 'url' ), |
| 306 |
width: this.model.get( 'width' ), |
| 307 |
height: this.model.get( 'height' ), |
| 308 |
orientation: this.model.get( 'orientation' ), |
| 309 |
}; |
| 310 |
}, |
| 311 |
|
| 312 |
checkClickHandler( event ) { |
| 313 |
const selection = this.options.selection; |
| 314 |
if ( ! selection ) { |
| 315 |
return; |
| 316 |
} |
| 317 |
event.stopPropagation(); |
| 318 |
if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { |
| 319 |
selection.remove( this.model ); |
| 320 |
// Move focus back to the attachment tile (from the check). |
| 321 |
this.$el.focus(); |
| 322 |
} else { |
| 323 |
selection.add( this.model ); |
| 324 |
} |
| 325 |
}, |
| 326 |
|
| 327 |
downloadClickHandler( event ) { |
| 328 |
this.controller.trigger( 'download-file', this.model, event.currentTarget ); |
| 329 |
}, |
| 330 |
|
| 331 |
} ); |
| 332 |
|
| 333 |
export default StockpackImage; |
| 334 |
|