| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 cr.define('options', function() { | 5 cr.define('options', function() { |
| 6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; | 6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; |
| 7 /** @const */ var Grid = cr.ui.Grid; | 7 /** @const */ var Grid = cr.ui.Grid; |
| 8 /** @const */ var GridItem = cr.ui.GridItem; | 8 /** @const */ var GridItem = cr.ui.GridItem; |
| 9 /** @const */ var GridSelectionController = cr.ui.GridSelectionController; | 9 /** @const */ var GridSelectionController = cr.ui.GridSelectionController; |
| 10 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; | 10 /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; |
| 11 | 11 |
| 12 /** | 12 /** |
| 13 * Dimensions for camera capture. | 13 * Dimensions for camera capture. |
| 14 * @const | 14 * @const |
| 15 */ | 15 */ |
| 16 var CAPTURE_SIZE = { | 16 var CAPTURE_SIZE = {height: 480, width: 480}; |
| 17 height: 480, | |
| 18 width: 480 | |
| 19 }; | |
| 20 | 17 |
| 21 /** | 18 /** |
| 22 * Path for internal URLs. | 19 * Path for internal URLs. |
| 23 * @const | 20 * @const |
| 24 */ | 21 */ |
| 25 var CHROME_THEME_PATH = 'chrome://theme'; | 22 var CHROME_THEME_PATH = 'chrome://theme'; |
| 26 | 23 |
| 27 /** | 24 /** |
| 28 * Creates a new user images grid item. | 25 * Creates a new user images grid item. |
| 29 * @param {{url: string, title: (string|undefined), | 26 * @param {{url: string, title: (string|undefined), |
| (...skipping 26 matching lines...) Expand all Loading... |
| 56 imageEl.title = this.dataItem.title || ''; | 53 imageEl.title = this.dataItem.title || ''; |
| 57 imageEl.alt = imageEl.title; | 54 imageEl.alt = imageEl.title; |
| 58 if (typeof this.dataItem.clickHandler == 'function') | 55 if (typeof this.dataItem.clickHandler == 'function') |
| 59 imageEl.addEventListener('mousedown', this.dataItem.clickHandler); | 56 imageEl.addEventListener('mousedown', this.dataItem.clickHandler); |
| 60 // Remove any garbage added by GridItem and ListItem decorators. | 57 // Remove any garbage added by GridItem and ListItem decorators. |
| 61 this.textContent = ''; | 58 this.textContent = ''; |
| 62 this.appendChild(imageEl); | 59 this.appendChild(imageEl); |
| 63 if (typeof this.dataItem.decorateFn == 'function') | 60 if (typeof this.dataItem.decorateFn == 'function') |
| 64 this.dataItem.decorateFn(this); | 61 this.dataItem.decorateFn(this); |
| 65 this.setAttribute('role', 'option'); | 62 this.setAttribute('role', 'option'); |
| 66 this.oncontextmenu = function(e) { e.preventDefault(); }; | 63 this.oncontextmenu = function(e) { |
| 64 e.preventDefault(); |
| 65 }; |
| 67 } | 66 } |
| 68 }; | 67 }; |
| 69 | 68 |
| 70 /** | 69 /** |
| 71 * Creates a selection controller that wraps selection on grid ends | 70 * Creates a selection controller that wraps selection on grid ends |
| 72 * and translates Enter presses into 'activate' events. | 71 * and translates Enter presses into 'activate' events. |
| 73 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to | 72 * @param {cr.ui.ListSelectionModel} selectionModel The selection model to |
| 74 * interact with. | 73 * interact with. |
| 75 * @param {cr.ui.Grid} grid The grid to interact with. | 74 * @param {cr.ui.Grid} grid The grid to interact with. |
| 76 * @constructor | 75 * @constructor |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 119 | 118 |
| 120 /** @override */ | 119 /** @override */ |
| 121 createSelectionController: function(sm) { | 120 createSelectionController: function(sm) { |
| 122 return new UserImagesGridSelectionController(sm, this); | 121 return new UserImagesGridSelectionController(sm, this); |
| 123 }, | 122 }, |
| 124 | 123 |
| 125 /** @override */ | 124 /** @override */ |
| 126 decorate: function() { | 125 decorate: function() { |
| 127 Grid.prototype.decorate.call(this); | 126 Grid.prototype.decorate.call(this); |
| 128 this.dataModel = new ArrayDataModel([]); | 127 this.dataModel = new ArrayDataModel([]); |
| 129 this.itemConstructor = /** @type {function(new:cr.ui.ListItem, *)} */( | 128 this.itemConstructor = |
| 130 UserImagesGridItem); | 129 /** @type {function(new:cr.ui.ListItem, *)} */ (UserImagesGridItem); |
| 131 this.selectionModel = new ListSingleSelectionModel(); | 130 this.selectionModel = new ListSingleSelectionModel(); |
| 132 this.inProgramSelection_ = false; | 131 this.inProgramSelection_ = false; |
| 133 this.addEventListener('dblclick', this.handleDblClick_.bind(this)); | 132 this.addEventListener('dblclick', this.handleDblClick_.bind(this)); |
| 134 this.addEventListener('change', this.handleChange_.bind(this)); | 133 this.addEventListener('change', this.handleChange_.bind(this)); |
| 135 this.setAttribute('role', 'listbox'); | 134 this.setAttribute('role', 'listbox'); |
| 136 this.autoExpands = true; | 135 this.autoExpands = true; |
| 137 }, | 136 }, |
| 138 | 137 |
| 139 /** | 138 /** |
| 140 * Handles double click on the image grid. | 139 * Handles double click on the image grid. |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 215 /** | 214 /** |
| 216 * Tries to starts camera stream capture. | 215 * Tries to starts camera stream capture. |
| 217 * @param {function(): boolean} onAvailable Callback that is called if | 216 * @param {function(): boolean} onAvailable Callback that is called if |
| 218 * camera is available. If it returns |true|, capture is started | 217 * camera is available. If it returns |true|, capture is started |
| 219 * immediately. | 218 * immediately. |
| 220 */ | 219 */ |
| 221 startCamera: function(onAvailable, onAbsent) { | 220 startCamera: function(onAvailable, onAbsent) { |
| 222 this.stopCamera(); | 221 this.stopCamera(); |
| 223 this.cameraStartInProgress_ = true; | 222 this.cameraStartInProgress_ = true; |
| 224 navigator.webkitGetUserMedia( | 223 navigator.webkitGetUserMedia( |
| 225 {video: true}, | 224 {video: true}, this.handleCameraAvailable_.bind(this, onAvailable), |
| 226 this.handleCameraAvailable_.bind(this, onAvailable), | |
| 227 this.handleCameraAbsent_.bind(this)); | 225 this.handleCameraAbsent_.bind(this)); |
| 228 }, | 226 }, |
| 229 | 227 |
| 230 /** | 228 /** |
| 231 * Stops camera capture, if it's currently active. | 229 * Stops camera capture, if it's currently active. |
| 232 */ | 230 */ |
| 233 stopCamera: function() { | 231 stopCamera: function() { |
| 234 this.cameraOnline = false; | 232 this.cameraOnline = false; |
| 235 if (this.cameraVideo_) | 233 if (this.cameraVideo_) |
| 236 this.cameraVideo_.src = ''; | 234 this.cameraVideo_.src = ''; |
| (...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 422 * @type {HTMLElement} | 420 * @type {HTMLElement} |
| 423 */ | 421 */ |
| 424 get previewElement() { | 422 get previewElement() { |
| 425 // TODO(ivankr): temporary hack for non-HTML5 version. | 423 // TODO(ivankr): temporary hack for non-HTML5 version. |
| 426 return this.previewElement_ || this; | 424 return this.previewElement_ || this; |
| 427 }, | 425 }, |
| 428 set previewElement(value) { | 426 set previewElement(value) { |
| 429 this.previewElement_ = value; | 427 this.previewElement_ = value; |
| 430 this.previewImage_ = value.querySelector('img'); | 428 this.previewImage_ = value.querySelector('img'); |
| 431 this.cameraVideo_ = value.querySelector('video'); | 429 this.cameraVideo_ = value.querySelector('video'); |
| 432 this.cameraVideo_.addEventListener('canplay', | 430 this.cameraVideo_.addEventListener( |
| 433 this.handleVideoStarted_.bind(this)); | 431 'canplay', this.handleVideoStarted_.bind(this)); |
| 434 this.cameraVideo_.addEventListener('timeupdate', | 432 this.cameraVideo_.addEventListener( |
| 435 this.handleVideoUpdate_.bind(this)); | 433 'timeupdate', this.handleVideoUpdate_.bind(this)); |
| 436 this.updatePreview_(); | 434 this.updatePreview_(); |
| 437 // Initialize camera state and check for its presence. | 435 // Initialize camera state and check for its presence. |
| 438 this.cameraLive = true; | 436 this.cameraLive = true; |
| 439 this.cameraPresent = false; | 437 this.cameraPresent = false; |
| 440 }, | 438 }, |
| 441 | 439 |
| 442 /** | 440 /** |
| 443 * Whether the camera live stream and photo should be flipped horizontally. | 441 * Whether the camera live stream and photo should be flipped horizontally. |
| 444 * If setting this property results in photo update, 'photoupdated' event | 442 * If setting this property results in photo update, 'photoupdated' event |
| 445 * will be fired with 'dataURL' property containing the photo encoded as | 443 * will be fired with 'dataURL' property containing the photo encoded as |
| 446 * a data URL | 444 * a data URL |
| 447 * @type {boolean} | 445 * @type {boolean} |
| 448 */ | 446 */ |
| 449 get flipPhoto() { | 447 get flipPhoto() { |
| 450 return this.flipPhoto_ || false; | 448 return this.flipPhoto_ || false; |
| 451 }, | 449 }, |
| 452 set flipPhoto(value) { | 450 set flipPhoto(value) { |
| 453 if (this.flipPhoto_ == value) | 451 if (this.flipPhoto_ == value) |
| 454 return; | 452 return; |
| 455 this.flipPhoto_ = value; | 453 this.flipPhoto_ = value; |
| 456 this.previewElement.classList.toggle('flip-x', value); | 454 this.previewElement.classList.toggle('flip-x', value); |
| 457 if (!this.cameraLive) { | 455 if (!this.cameraLive) { |
| 458 // Flip current still photo. | 456 // Flip current still photo. |
| 459 var e = new Event('photoupdated'); | 457 var e = new Event('photoupdated'); |
| 460 e.dataURL = this.flipPhoto ? | 458 e.dataURL = this.flipPhoto ? this.flipFrame_(this.previewImage_) : |
| 461 this.flipFrame_(this.previewImage_) : this.previewImage_.src; | 459 this.previewImage_.src; |
| 462 this.dispatchEvent(e); | 460 this.dispatchEvent(e); |
| 463 } | 461 } |
| 464 }, | 462 }, |
| 465 | 463 |
| 466 /** | 464 /** |
| 467 * Performs photo capture from the live camera stream. 'phototaken' event | 465 * Performs photo capture from the live camera stream. 'phototaken' event |
| 468 * will be fired as soon as captured photo is available, with 'dataURL' | 466 * will be fired as soon as captured photo is available, with 'dataURL' |
| 469 * property containing the photo encoded as a data URL. | 467 * property containing the photo encoded as a data URL. |
| 470 * @return {boolean} Whether photo capture was successful. | 468 * @return {boolean} Whether photo capture was successful. |
| 471 */ | 469 */ |
| 472 takePhoto: function() { | 470 takePhoto: function() { |
| 473 if (!this.cameraOnline) | 471 if (!this.cameraOnline) |
| 474 return false; | 472 return false; |
| 475 var canvas = /** @type {HTMLCanvasElement} */( | 473 var canvas = |
| 476 document.createElement('canvas')); | 474 /** @type {HTMLCanvasElement} */ (document.createElement('canvas')); |
| 477 canvas.width = CAPTURE_SIZE.width; | 475 canvas.width = CAPTURE_SIZE.width; |
| 478 canvas.height = CAPTURE_SIZE.height; | 476 canvas.height = CAPTURE_SIZE.height; |
| 479 this.captureFrame_( | 477 this.captureFrame_( |
| 480 this.cameraVideo_, | 478 this.cameraVideo_, |
| 481 /** @type {CanvasRenderingContext2D} */(canvas.getContext('2d')), | 479 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')), |
| 482 CAPTURE_SIZE); | 480 CAPTURE_SIZE); |
| 483 // Preload image before displaying it. | 481 // Preload image before displaying it. |
| 484 var previewImg = new Image(); | 482 var previewImg = new Image(); |
| 485 previewImg.addEventListener('load', function(e) { | 483 previewImg.addEventListener('load', function(e) { |
| 486 this.cameraTitle_ = this.capturedImageTitle_; | 484 this.cameraTitle_ = this.capturedImageTitle_; |
| 487 this.cameraImage = previewImg.src; | 485 this.cameraImage = previewImg.src; |
| 488 }.bind(this)); | 486 }.bind(this)); |
| 489 previewImg.src = canvas.toDataURL('image/png'); | 487 previewImg.src = canvas.toDataURL('image/png'); |
| 490 var e = new Event('phototaken'); | 488 var e = new Event('phototaken'); |
| 491 e.dataURL = this.flipPhoto ? this.flipFrame_(canvas) : previewImg.src; | 489 e.dataURL = this.flipPhoto ? this.flipFrame_(canvas) : previewImg.src; |
| (...skipping 14 matching lines...) Expand all Loading... |
| 506 * current drawing origin of a canvas context. | 504 * current drawing origin of a canvas context. |
| 507 * @param {HTMLVideoElement} video Video element to capture from. | 505 * @param {HTMLVideoElement} video Video element to capture from. |
| 508 * @param {CanvasRenderingContext2D} ctx Canvas context to draw onto. | 506 * @param {CanvasRenderingContext2D} ctx Canvas context to draw onto. |
| 509 * @param {{width: number, height: number}} destSize Capture size. | 507 * @param {{width: number, height: number}} destSize Capture size. |
| 510 * @private | 508 * @private |
| 511 */ | 509 */ |
| 512 captureFrame_: function(video, ctx, destSize) { | 510 captureFrame_: function(video, ctx, destSize) { |
| 513 var width = video.videoWidth; | 511 var width = video.videoWidth; |
| 514 var height = video.videoHeight; | 512 var height = video.videoHeight; |
| 515 if (width < destSize.width || height < destSize.height) { | 513 if (width < destSize.width || height < destSize.height) { |
| 516 console.error('Video capture size too small: ' + | 514 console.error( |
| 517 width + 'x' + height + '!'); | 515 'Video capture size too small: ' + width + 'x' + height + '!'); |
| 518 } | 516 } |
| 519 var src = {}; | 517 var src = {}; |
| 520 if (width / destSize.width > height / destSize.height) { | 518 if (width / destSize.width > height / destSize.height) { |
| 521 // Full height, crop left/right. | 519 // Full height, crop left/right. |
| 522 src.height = height; | 520 src.height = height; |
| 523 src.width = height * destSize.width / destSize.height; | 521 src.width = height * destSize.width / destSize.height; |
| 524 } else { | 522 } else { |
| 525 // Full width, crop top/bottom. | 523 // Full width, crop top/bottom. |
| 526 src.width = width; | 524 src.width = width; |
| 527 src.height = width * destSize.height / destSize.width; | 525 src.height = width * destSize.height / destSize.width; |
| 528 } | 526 } |
| 529 src.x = (width - src.width) / 2; | 527 src.x = (width - src.width) / 2; |
| 530 src.y = (height - src.height) / 2; | 528 src.y = (height - src.height) / 2; |
| 531 ctx.drawImage(video, src.x, src.y, src.width, src.height, | 529 ctx.drawImage( |
| 532 0, 0, destSize.width, destSize.height); | 530 video, src.x, src.y, src.width, src.height, 0, 0, destSize.width, |
| 531 destSize.height); |
| 533 }, | 532 }, |
| 534 | 533 |
| 535 /** | 534 /** |
| 536 * Flips frame horizontally. | 535 * Flips frame horizontally. |
| 537 * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} source | 536 * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} source |
| 538 * Frame to flip. | 537 * Frame to flip. |
| 539 * @return {string} Flipped frame as data URL. | 538 * @return {string} Flipped frame as data URL. |
| 540 */ | 539 */ |
| 541 flipFrame_: function(source) { | 540 flipFrame_: function(source) { |
| 542 var canvas = document.createElement('canvas'); | 541 var canvas = document.createElement('canvas'); |
| (...skipping 11 matching lines...) Expand all Loading... |
| 554 * @param {string} url Image URL. | 553 * @param {string} url Image URL. |
| 555 * @param {string=} opt_title Image tooltip. | 554 * @param {string=} opt_title Image tooltip. |
| 556 * @param {Function=} opt_clickHandler Image click handler. | 555 * @param {Function=} opt_clickHandler Image click handler. |
| 557 * @param {number=} opt_position If given, inserts new image into | 556 * @param {number=} opt_position If given, inserts new image into |
| 558 * that position (0-based) in image list. | 557 * that position (0-based) in image list. |
| 559 * @param {Function=} opt_decorateFn Function called with the list element | 558 * @param {Function=} opt_decorateFn Function called with the list element |
| 560 * as argument to do any final decoration. | 559 * as argument to do any final decoration. |
| 561 * @return {!Object} Image data inserted into the data model. | 560 * @return {!Object} Image data inserted into the data model. |
| 562 */ | 561 */ |
| 563 // TODO(ivankr): this function needs some argument list refactoring. | 562 // TODO(ivankr): this function needs some argument list refactoring. |
| 564 addItem: function(url, opt_title, opt_clickHandler, opt_position, | 563 addItem: function( |
| 565 opt_decorateFn) { | 564 url, opt_title, opt_clickHandler, opt_position, opt_decorateFn) { |
| 566 var imageInfo = { | 565 var imageInfo = { |
| 567 url: url, | 566 url: url, |
| 568 title: opt_title, | 567 title: opt_title, |
| 569 clickHandler: opt_clickHandler, | 568 clickHandler: opt_clickHandler, |
| 570 decorateFn: opt_decorateFn | 569 decorateFn: opt_decorateFn |
| 571 }; | 570 }; |
| 572 this.inProgramSelection_ = true; | 571 this.inProgramSelection_ = true; |
| 573 if (opt_position !== undefined) | 572 if (opt_position !== undefined) |
| 574 this.dataModel.splice(opt_position, 0, imageInfo); | 573 this.dataModel.splice(opt_position, 0, imageInfo); |
| 575 else | 574 else |
| (...skipping 17 matching lines...) Expand all Loading... |
| 593 * @param {string} imageUrl New image URL. | 592 * @param {string} imageUrl New image URL. |
| 594 * @param {string=} opt_title New image tooltip (if undefined, tooltip | 593 * @param {string=} opt_title New image tooltip (if undefined, tooltip |
| 595 * is left unchanged). | 594 * is left unchanged). |
| 596 * @return {!Object} Image data of the added or updated image. | 595 * @return {!Object} Image data of the added or updated image. |
| 597 */ | 596 */ |
| 598 updateItem: function(imageInfo, imageUrl, opt_title) { | 597 updateItem: function(imageInfo, imageUrl, opt_title) { |
| 599 var imageIndex = this.indexOf(imageInfo); | 598 var imageIndex = this.indexOf(imageInfo); |
| 600 var wasSelected = this.selectionModel.selectedIndex == imageIndex; | 599 var wasSelected = this.selectionModel.selectedIndex == imageIndex; |
| 601 this.removeItem(imageInfo); | 600 this.removeItem(imageInfo); |
| 602 var newInfo = this.addItem( | 601 var newInfo = this.addItem( |
| 603 imageUrl, | 602 imageUrl, opt_title === undefined ? imageInfo.title : opt_title, |
| 604 opt_title === undefined ? imageInfo.title : opt_title, | 603 imageInfo.clickHandler, imageIndex, imageInfo.decorateFn); |
| 605 imageInfo.clickHandler, | |
| 606 imageIndex, | |
| 607 imageInfo.decorateFn); | |
| 608 // Update image data with the reset of the keys from the old data. | 604 // Update image data with the reset of the keys from the old data. |
| 609 for (var k in imageInfo) { | 605 for (var k in imageInfo) { |
| 610 if (!(k in newInfo)) | 606 if (!(k in newInfo)) |
| 611 newInfo[k] = imageInfo[k]; | 607 newInfo[k] = imageInfo[k]; |
| 612 } | 608 } |
| 613 if (wasSelected) | 609 if (wasSelected) |
| 614 this.selectedItem = newInfo; | 610 this.selectedItem = newInfo; |
| 615 return newInfo; | 611 return newInfo; |
| 616 }, | 612 }, |
| 617 | 613 |
| 618 /** | 614 /** |
| 619 * Removes previously added image from the grid. | 615 * Removes previously added image from the grid. |
| 620 * @param {Object} imageInfo Image data returned from the addItem() call. | 616 * @param {Object} imageInfo Image data returned from the addItem() call. |
| 621 */ | 617 */ |
| 622 removeItem: function(imageInfo) { | 618 removeItem: function(imageInfo) { |
| 623 var index = this.indexOf(imageInfo); | 619 var index = this.indexOf(imageInfo); |
| 624 if (index != -1) { | 620 if (index != -1) { |
| 625 var wasSelected = this.selectionModel.selectedIndex == index; | 621 var wasSelected = this.selectionModel.selectedIndex == index; |
| 626 this.inProgramSelection_ = true; | 622 this.inProgramSelection_ = true; |
| 627 this.dataModel.splice(index, 1); | 623 this.dataModel.splice(index, 1); |
| 628 if (wasSelected) { | 624 if (wasSelected) { |
| 629 // If item removed was selected, select the item next to it. | 625 // If item removed was selected, select the item next to it. |
| 630 this.selectedItem = this.dataModel.item( | 626 this.selectedItem = |
| 631 Math.min(this.dataModel.length - 1, index)); | 627 this.dataModel.item(Math.min(this.dataModel.length - 1, index)); |
| 632 } | 628 } |
| 633 this.inProgramSelection_ = false; | 629 this.inProgramSelection_ = false; |
| 634 } | 630 } |
| 635 }, | 631 }, |
| 636 | 632 |
| 637 /** | 633 /** |
| 638 * Forces re-display, size re-calculation and focuses grid. | 634 * Forces re-display, size re-calculation and focuses grid. |
| 639 */ | 635 */ |
| 640 updateAndFocus: function() { | 636 updateAndFocus: function() { |
| 641 // Recalculate the measured item size. | 637 // Recalculate the measured item size. |
| (...skipping 23 matching lines...) Expand all Loading... |
| 665 /** | 661 /** |
| 666 * URLs of special button images. | 662 * URLs of special button images. |
| 667 * @enum {string} | 663 * @enum {string} |
| 668 */ | 664 */ |
| 669 UserImagesGrid.ButtonImages = { | 665 UserImagesGrid.ButtonImages = { |
| 670 TAKE_PHOTO: 'chrome://theme/IDR_BUTTON_USER_IMAGE_TAKE_PHOTO', | 666 TAKE_PHOTO: 'chrome://theme/IDR_BUTTON_USER_IMAGE_TAKE_PHOTO', |
| 671 CHOOSE_FILE: 'chrome://theme/IDR_BUTTON_USER_IMAGE_CHOOSE_FILE', | 667 CHOOSE_FILE: 'chrome://theme/IDR_BUTTON_USER_IMAGE_CHOOSE_FILE', |
| 672 PROFILE_PICTURE: 'chrome://theme/IDR_PROFILE_PICTURE_LOADING' | 668 PROFILE_PICTURE: 'chrome://theme/IDR_PROFILE_PICTURE_LOADING' |
| 673 }; | 669 }; |
| 674 | 670 |
| 675 return { | 671 return {UserImagesGrid: UserImagesGrid}; |
| 676 UserImagesGrid: UserImagesGrid | |
| 677 }; | |
| 678 }); | 672 }); |
| OLD | NEW |