| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 // API invoked by the browser MediaRouterWebUIMessageHandler to communicate | 5 // API invoked by the browser MediaRouterWebUIMessageHandler to communicate |
| 6 // with this UI. | 6 // with this UI. |
| 7 cr.define('media_router.ui', function() { | 7 cr.define('media_router.ui', function() { |
| 8 'use strict'; | 8 'use strict'; |
| 9 | 9 |
| 10 // The media-router-container element. | 10 // The media-router-container element. |
| 11 var container = null; | 11 var container = null; |
| 12 | 12 |
| 13 // The media-router-header element. | 13 // The media-router-header element. |
| 14 var header = null; | 14 var header = null; |
| 15 | 15 |
| 16 // The route-controls element. Is null if the route details view isn't open. |
| 17 var routeControls = null; |
| 18 |
| 16 /** | 19 /** |
| 17 * Handles response of previous create route attempt. | 20 * Handles response of previous create route attempt. |
| 18 * | 21 * |
| 19 * @param {string} sinkId The ID of the sink to which the Media Route was | 22 * @param {string} sinkId The ID of the sink to which the Media Route was |
| 20 * creating a route. | 23 * creating a route. |
| 21 * @param {?media_router.Route} route The newly created route that | 24 * @param {?media_router.Route} route The newly created route that |
| 22 * corresponds to the sink if route creation succeeded; null otherwise. | 25 * corresponds to the sink if route creation succeeded; null otherwise. |
| 23 * @param {boolean} isForDisplay Whether or not |route| is for display. | 26 * @param {boolean} isForDisplay Whether or not |route| is for display. |
| 24 */ | 27 */ |
| 25 function onCreateRouteResponseReceived(sinkId, route, isForDisplay) { | 28 function onCreateRouteResponseReceived(sinkId, route, isForDisplay) { |
| 26 container.onCreateRouteResponseReceived(sinkId, route, isForDisplay); | 29 container.onCreateRouteResponseReceived(sinkId, route, isForDisplay); |
| 27 } | 30 } |
| 28 | 31 |
| 29 /** | 32 /** |
| 33 * Called when the route controller for the route that is currently selected |
| 34 * is invalidated. |
| 35 */ |
| 36 function onRouteControllerInvalidated() { |
| 37 container.onRouteControllerInvalidated(); |
| 38 } |
| 39 |
| 40 /** |
| 30 * Handles the search response by forwarding |sinkId| to the container. | 41 * Handles the search response by forwarding |sinkId| to the container. |
| 31 * | 42 * |
| 32 * @param {string} sinkId The ID of the sink found by search. | 43 * @param {string} sinkId The ID of the sink found by search. |
| 33 */ | 44 */ |
| 34 function receiveSearchResult(sinkId) { | 45 function receiveSearchResult(sinkId) { |
| 35 container.onReceiveSearchResult(sinkId); | 46 container.onReceiveSearchResult(sinkId); |
| 36 } | 47 } |
| 37 | 48 |
| 38 /** | 49 /** |
| 39 * Sets the cast mode list. | 50 * Sets the cast mode list. |
| 40 * | 51 * |
| 41 * @param {!Array<!media_router.CastMode>} castModeList | 52 * @param {!Array<!media_router.CastMode>} castModeList |
| 42 */ | 53 */ |
| 43 function setCastModeList(castModeList) { | 54 function setCastModeList(castModeList) { |
| 44 container.castModeList = castModeList; | 55 container.castModeList = castModeList; |
| 45 } | 56 } |
| 46 | 57 |
| 47 /** | 58 /** |
| 48 * Sets |container| and |header|. | 59 * Sets |container| and |header|. |
| 49 * | 60 * |
| 50 * @param {!MediaRouterContainerElement} mediaRouterContainer | 61 * @param {!MediaRouterContainerInterface} mediaRouterContainer |
| 51 * @param {!MediaRouterHeaderElement} mediaRouterHeader | 62 * @param {!MediaRouterHeaderElement} mediaRouterHeader |
| 52 */ | 63 */ |
| 53 function setElements(mediaRouterContainer, mediaRouterHeader) { | 64 function setElements(mediaRouterContainer, mediaRouterHeader) { |
| 54 container = mediaRouterContainer; | 65 container = mediaRouterContainer; |
| 55 header = mediaRouterHeader; | 66 header = mediaRouterHeader; |
| 56 } | 67 } |
| 57 | 68 |
| 58 /** | 69 /** |
| 59 * Populates the WebUI with data obtained about the first run flow. | 70 * Populates the WebUI with data obtained about the first run flow. |
| 60 * | 71 * |
| 61 * @param {{firstRunFlowCloudPrefLearnMoreUrl: string, | 72 * @param {{firstRunFlowCloudPrefLearnMoreUrl: string, |
| 62 * firstRunFlowLearnMoreUrl: string, | 73 * firstRunFlowLearnMoreUrl: string, |
| 63 * wasFirstRunFlowAcknowledged: boolean, | 74 * wasFirstRunFlowAcknowledged: boolean, |
| 64 * showFirstRunFlowCloudPref: boolean}} data | 75 * showFirstRunFlowCloudPref: boolean}} data |
| 65 * Parameters in data: | 76 * Parameters in data: |
| 66 * firstRunFlowCloudPrefLearnMoreUrl - url to open when the cloud services | 77 * firstRunFlowCloudPrefLearnMoreUrl - url to open when the cloud services |
| 67 * pref learn more link is clicked. | 78 * pref learn more link is clicked. |
| 68 * firstRunFlowLearnMoreUrl - url to open when the first run flow learn | 79 * firstRunFlowLearnMoreUrl - url to open when the first run flow learn |
| 69 * more link is clicked. | 80 * more link is clicked. |
| 70 * wasFirstRunFlowAcknowledged - true if first run flow was previously | 81 * wasFirstRunFlowAcknowledged - true if first run flow was previously |
| 71 * acknowledged by user. | 82 * acknowledged by user. |
| 72 * showFirstRunFlowCloudPref - true if the cloud pref option should be | 83 * showFirstRunFlowCloudPref - true if the cloud pref option should be |
| 73 * shown. | 84 * shown. |
| 74 */ | 85 */ |
| 75 function setFirstRunFlowData(data) { | 86 function setFirstRunFlowData(data) { |
| 76 container.firstRunFlowCloudPrefLearnMoreUrl = | 87 container.firstRunFlowCloudPrefLearnMoreUrl = |
| 77 data['firstRunFlowCloudPrefLearnMoreUrl']; | 88 data['firstRunFlowCloudPrefLearnMoreUrl']; |
| 78 container.firstRunFlowLearnMoreUrl = | 89 container.firstRunFlowLearnMoreUrl = data['firstRunFlowLearnMoreUrl']; |
| 79 data['firstRunFlowLearnMoreUrl']; | 90 container.showFirstRunFlowCloudPref = data['showFirstRunFlowCloudPref']; |
| 80 container.showFirstRunFlowCloudPref = | |
| 81 data['showFirstRunFlowCloudPref']; | |
| 82 // Some users acknowledged the first run flow before the cloud prefs | 91 // Some users acknowledged the first run flow before the cloud prefs |
| 83 // setting was implemented. These users will see the first run flow | 92 // setting was implemented. These users will see the first run flow |
| 84 // again. | 93 // again. |
| 85 container.showFirstRunFlow = !data['wasFirstRunFlowAcknowledged'] || | 94 container.showFirstRunFlow = !data['wasFirstRunFlowAcknowledged'] || |
| 86 container.showFirstRunFlowCloudPref; | 95 container.showFirstRunFlowCloudPref; |
| 87 } | 96 } |
| 88 | 97 |
| 89 /** | 98 /** |
| 90 * Populates the WebUI with data obtained from Media Router. | 99 * Populates the WebUI with data obtained from Media Router. |
| 91 * | 100 * |
| 92 * @param {{deviceMissingUrl: string, | 101 * @param {{deviceMissingUrl: string, |
| 93 * sinksAndIdentity: { | 102 * sinksAndIdentity: { |
| 94 * sinks: !Array<!media_router.Sink>, | 103 * sinks: !Array<!media_router.Sink>, |
| 95 * showEmail: boolean, | 104 * showEmail: boolean, |
| 96 * userEmail: string, | 105 * userEmail: string, |
| 97 * showDomain: boolean | 106 * showDomain: boolean |
| 98 * }, | 107 * }, |
| 99 * routes: !Array<!media_router.Route>, | 108 * routes: !Array<!media_router.Route>, |
| 100 * castModes: !Array<!media_router.CastMode>, | 109 * castModes: !Array<!media_router.CastMode>, |
| 101 * useTabMirroring: boolean}} data | 110 * useTabMirroring: boolean}} data |
| 102 * Parameters in data: | 111 * Parameters in data: |
| 103 * deviceMissingUrl - url to be opened on "Device missing?" clicked. | 112 * deviceMissingUrl - url to be opened on "Device missing?" clicked. |
| 104 * sinksAndIdentity - list of sinks to be displayed and user identity. | 113 * sinksAndIdentity - list of sinks to be displayed and user identity. |
| 114 * useWebUiRouteControls - whether new WebUI route controls should be used. |
| 105 * routes - list of routes that are associated with the sinks. | 115 * routes - list of routes that are associated with the sinks. |
| 106 * castModes - list of available cast modes. | 116 * castModes - list of available cast modes. |
| 107 * useTabMirroring - whether the cast mode should be set to TAB_MIRROR. | 117 * useTabMirroring - whether the cast mode should be set to TAB_MIRROR. |
| 108 */ | 118 */ |
| 109 function setInitialData(data) { | 119 function setInitialData(data) { |
| 110 container.deviceMissingUrl = data['deviceMissingUrl']; | 120 container.deviceMissingUrl = data['deviceMissingUrl']; |
| 111 container.castModeList = data['castModes']; | 121 container.castModeList = data['castModes']; |
| 112 this.setSinkListAndIdentity(data['sinksAndIdentity']); | 122 this.setSinkListAndIdentity(data['sinksAndIdentity']); |
| 123 container.useWebUiRouteControls = !!data['useWebUiRouteControls']; |
| 113 container.routeList = data['routes']; | 124 container.routeList = data['routes']; |
| 114 container.maybeShowRouteDetailsOnOpen(); | 125 container.maybeShowRouteDetailsOnOpen(); |
| 115 if (data['useTabMirroring']) | 126 if (data['useTabMirroring']) |
| 116 container.selectCastMode(media_router.CastModeType.TAB_MIRROR); | 127 container.selectCastMode(media_router.CastModeType.TAB_MIRROR); |
| 117 media_router.browserApi.onInitialDataReceived(); | 128 media_router.browserApi.onInitialDataReceived(); |
| 118 } | 129 } |
| 119 | 130 |
| 120 /** | 131 /** |
| 121 * Sets current issue to |issue|, or clears the current issue if |issue| is | 132 * Sets current issue to |issue|, or clears the current issue if |issue| is |
| 122 * null. | 133 * null. |
| 123 * | 134 * |
| 124 * @param {?media_router.Issue} issue | 135 * @param {?media_router.Issue} issue |
| 125 */ | 136 */ |
| 126 function setIssue(issue) { | 137 function setIssue(issue) { |
| 127 container.issue = issue; | 138 container.issue = issue; |
| 128 } | 139 } |
| 129 | 140 |
| 130 /** | 141 /** |
| 142 * Sets |routeControls|. The argument may be null if the route details view is |
| 143 * getting closed. |
| 144 * |
| 145 * @param {?RouteControlsInterface} mediaRouterRouteControls |
| 146 */ |
| 147 function setRouteControls(mediaRouterRouteControls) { |
| 148 routeControls = mediaRouterRouteControls; |
| 149 } |
| 150 |
| 151 /** |
| 131 * Sets the list of currently active routes. | 152 * Sets the list of currently active routes. |
| 132 * | 153 * |
| 133 * @param {!Array<!media_router.Route>} routeList | 154 * @param {!Array<!media_router.Route>} routeList |
| 134 */ | 155 */ |
| 135 function setRouteList(routeList) { | 156 function setRouteList(routeList) { |
| 136 container.routeList = routeList; | 157 container.routeList = routeList; |
| 137 } | 158 } |
| 138 | 159 |
| 139 /** | 160 /** |
| 140 * Sets the list of discovered sinks along with properties of whether to hide | 161 * Sets the list of discovered sinks along with properties of whether to hide |
| (...skipping 18 matching lines...) Expand all Loading... |
| 159 | 180 |
| 160 /** | 181 /** |
| 161 * Updates the max height of the dialog | 182 * Updates the max height of the dialog |
| 162 * | 183 * |
| 163 * @param {number} height | 184 * @param {number} height |
| 164 */ | 185 */ |
| 165 function updateMaxHeight(height) { | 186 function updateMaxHeight(height) { |
| 166 container.updateMaxDialogHeight(height); | 187 container.updateMaxDialogHeight(height); |
| 167 } | 188 } |
| 168 | 189 |
| 190 /** |
| 191 * Updates the route status shown in the route controls. |
| 192 * |
| 193 * @param {!media_router.RouteStatus} status |
| 194 */ |
| 195 function updateRouteStatus(status) { |
| 196 if (routeControls) { |
| 197 routeControls.routeStatus = status; |
| 198 } |
| 199 } |
| 200 |
| 169 return { | 201 return { |
| 170 onCreateRouteResponseReceived: onCreateRouteResponseReceived, | 202 onCreateRouteResponseReceived: onCreateRouteResponseReceived, |
| 203 onRouteControllerInvalidated: onRouteControllerInvalidated, |
| 171 receiveSearchResult: receiveSearchResult, | 204 receiveSearchResult: receiveSearchResult, |
| 172 setCastModeList: setCastModeList, | 205 setCastModeList: setCastModeList, |
| 173 setElements: setElements, | 206 setElements: setElements, |
| 174 setFirstRunFlowData: setFirstRunFlowData, | 207 setFirstRunFlowData: setFirstRunFlowData, |
| 175 setInitialData: setInitialData, | 208 setInitialData: setInitialData, |
| 176 setIssue: setIssue, | 209 setIssue: setIssue, |
| 210 setRouteControls: setRouteControls, |
| 177 setRouteList: setRouteList, | 211 setRouteList: setRouteList, |
| 178 setSinkListAndIdentity: setSinkListAndIdentity, | 212 setSinkListAndIdentity: setSinkListAndIdentity, |
| 179 updateMaxHeight: updateMaxHeight, | 213 updateMaxHeight: updateMaxHeight, |
| 214 updateRouteStatus: updateRouteStatus, |
| 180 }; | 215 }; |
| 181 }); | 216 }); |
| 182 | |
| 183 // API invoked by this UI to communicate with the browser WebUI message handler. | |
| 184 cr.define('media_router.browserApi', function() { | |
| 185 'use strict'; | |
| 186 | |
| 187 /** | |
| 188 * Indicates that the user has acknowledged the first run flow. | |
| 189 * | |
| 190 * @param {boolean} optedIntoCloudServices Whether or not the user opted into | |
| 191 * cloud services. | |
| 192 */ | |
| 193 function acknowledgeFirstRunFlow(optedIntoCloudServices) { | |
| 194 chrome.send('acknowledgeFirstRunFlow', [optedIntoCloudServices]); | |
| 195 } | |
| 196 | |
| 197 /** | |
| 198 * Acts on the given issue. | |
| 199 * | |
| 200 * @param {number} issueId | |
| 201 * @param {number} actionType Type of action that the user clicked. | |
| 202 * @param {?number} helpPageId The numeric help center ID. | |
| 203 */ | |
| 204 function actOnIssue(issueId, actionType, helpPageId) { | |
| 205 chrome.send('actOnIssue', [{issueId: issueId, actionType: actionType, | |
| 206 helpPageId: helpPageId}]); | |
| 207 } | |
| 208 | |
| 209 /** | |
| 210 * Modifies |route| by changing its source to the one identified by | |
| 211 * |selectedCastMode|. | |
| 212 * | |
| 213 * @param {!media_router.Route} route The route being modified. | |
| 214 * @param {number} selectedCastMode The value of the cast mode the user | |
| 215 * selected. | |
| 216 */ | |
| 217 function changeRouteSource(route, selectedCastMode) { | |
| 218 chrome.send('requestRoute', | |
| 219 [{sinkId: route.sinkId, selectedCastMode: selectedCastMode}]); | |
| 220 } | |
| 221 | |
| 222 /** | |
| 223 * Closes the dialog. | |
| 224 * | |
| 225 * @param {boolean} pressEscToClose Whether the user pressed ESC to close the | |
| 226 * dialog. | |
| 227 */ | |
| 228 function closeDialog(pressEscToClose) { | |
| 229 chrome.send('closeDialog', [pressEscToClose]); | |
| 230 } | |
| 231 | |
| 232 /** | |
| 233 * Closes the given route. | |
| 234 * | |
| 235 * @param {!media_router.Route} route | |
| 236 */ | |
| 237 function closeRoute(route) { | |
| 238 chrome.send('closeRoute', [{routeId: route.id, isLocal: route.isLocal}]); | |
| 239 } | |
| 240 | |
| 241 /** | |
| 242 * Joins the given route. | |
| 243 * | |
| 244 * @param {!media_router.Route} route | |
| 245 */ | |
| 246 function joinRoute(route) { | |
| 247 chrome.send('joinRoute', [{sinkId: route.sinkId, routeId: route.id}]); | |
| 248 } | |
| 249 | |
| 250 /** | |
| 251 * Indicates that the initial data has been received. | |
| 252 */ | |
| 253 function onInitialDataReceived() { | |
| 254 chrome.send('onInitialDataReceived'); | |
| 255 } | |
| 256 | |
| 257 /** | |
| 258 * Reports when the user clicks outside the dialog. | |
| 259 */ | |
| 260 function reportBlur() { | |
| 261 chrome.send('reportBlur'); | |
| 262 } | |
| 263 | |
| 264 /** | |
| 265 * Reports the index of the selected sink. | |
| 266 * | |
| 267 * @param {number} sinkIndex | |
| 268 */ | |
| 269 function reportClickedSinkIndex(sinkIndex) { | |
| 270 chrome.send('reportClickedSinkIndex', [sinkIndex]); | |
| 271 } | |
| 272 | |
| 273 /** | |
| 274 * Reports that the user used the filter input. | |
| 275 */ | |
| 276 function reportFilter() { | |
| 277 chrome.send('reportFilter'); | |
| 278 } | |
| 279 | |
| 280 /** | |
| 281 * Reports the initial dialog view. | |
| 282 * | |
| 283 * @param {string} view | |
| 284 */ | |
| 285 function reportInitialState(view) { | |
| 286 chrome.send('reportInitialState', [view]); | |
| 287 } | |
| 288 | |
| 289 /** | |
| 290 * Reports the initial action the user took. | |
| 291 * | |
| 292 * @param {number} action | |
| 293 */ | |
| 294 function reportInitialAction(action) { | |
| 295 chrome.send('reportInitialAction', [action]); | |
| 296 } | |
| 297 | |
| 298 /** | |
| 299 * Reports the navigation to the specified view. | |
| 300 * | |
| 301 * @param {string} view | |
| 302 */ | |
| 303 function reportNavigateToView(view) { | |
| 304 chrome.send('reportNavigateToView', [view]); | |
| 305 } | |
| 306 | |
| 307 /** | |
| 308 * Reports whether or not a route was created successfully. | |
| 309 * | |
| 310 * @param {boolean} success | |
| 311 */ | |
| 312 function reportRouteCreation(success) { | |
| 313 chrome.send('reportRouteCreation', [success]); | |
| 314 } | |
| 315 | |
| 316 /** | |
| 317 * Reports the outcome of a create route response. | |
| 318 * | |
| 319 * @param {number} outcome | |
| 320 */ | |
| 321 function reportRouteCreationOutcome(outcome) { | |
| 322 chrome.send('reportRouteCreationOutcome', [outcome]); | |
| 323 } | |
| 324 | |
| 325 /** | |
| 326 * Reports the cast mode that the user selected. | |
| 327 * | |
| 328 * @param {number} castModeType | |
| 329 */ | |
| 330 function reportSelectedCastMode(castModeType) { | |
| 331 chrome.send('reportSelectedCastMode', [castModeType]); | |
| 332 } | |
| 333 | |
| 334 /** | |
| 335 * Reports the current number of sinks. | |
| 336 * | |
| 337 * @param {number} sinkCount | |
| 338 */ | |
| 339 function reportSinkCount(sinkCount) { | |
| 340 chrome.send('reportSinkCount', [sinkCount]); | |
| 341 } | |
| 342 | |
| 343 /** | |
| 344 * Reports the time it took for the user to select a sink after the sink list | |
| 345 * is populated and shown. | |
| 346 * | |
| 347 * @param {number} timeMs | |
| 348 */ | |
| 349 function reportTimeToClickSink(timeMs) { | |
| 350 chrome.send('reportTimeToClickSink', [timeMs]); | |
| 351 } | |
| 352 | |
| 353 /** | |
| 354 * Reports the time, in ms, it took for the user to close the dialog without | |
| 355 * taking any other action. | |
| 356 * | |
| 357 * @param {number} timeMs | |
| 358 */ | |
| 359 function reportTimeToInitialActionClose(timeMs) { | |
| 360 chrome.send('reportTimeToInitialActionClose', [timeMs]); | |
| 361 } | |
| 362 | |
| 363 /** | |
| 364 * Requests data to initialize the WebUI with. | |
| 365 * The data will be returned via media_router.ui.setInitialData. | |
| 366 */ | |
| 367 function requestInitialData() { | |
| 368 chrome.send('requestInitialData'); | |
| 369 } | |
| 370 | |
| 371 /** | |
| 372 * Requests that a media route be started with the given sink. | |
| 373 * | |
| 374 * @param {string} sinkId The sink ID. | |
| 375 * @param {number} selectedCastMode The value of the cast mode the user | |
| 376 * selected. | |
| 377 */ | |
| 378 function requestRoute(sinkId, selectedCastMode) { | |
| 379 chrome.send('requestRoute', | |
| 380 [{sinkId: sinkId, selectedCastMode: selectedCastMode}]); | |
| 381 } | |
| 382 | |
| 383 /** | |
| 384 * Requests that the media router search all providers for a sink matching | |
| 385 * |searchCriteria| that can be used with the media source associated with the | |
| 386 * cast mode |selectedCastMode|. If such a sink is found, a route is also | |
| 387 * created between the sink and the media source. | |
| 388 * | |
| 389 * @param {string} sinkId Sink ID of the pseudo sink generating the request. | |
| 390 * @param {string} searchCriteria Search criteria for the route providers. | |
| 391 * @param {string} domain User's current hosted domain. | |
| 392 * @param {number} selectedCastMode The value of the cast mode to be used with | |
| 393 * the sink. | |
| 394 */ | |
| 395 function searchSinksAndCreateRoute( | |
| 396 sinkId, searchCriteria, domain, selectedCastMode) { | |
| 397 chrome.send('searchSinksAndCreateRoute', | |
| 398 [{sinkId: sinkId, | |
| 399 searchCriteria: searchCriteria, | |
| 400 domain: domain, | |
| 401 selectedCastMode: selectedCastMode}]); | |
| 402 } | |
| 403 | |
| 404 return { | |
| 405 acknowledgeFirstRunFlow: acknowledgeFirstRunFlow, | |
| 406 actOnIssue: actOnIssue, | |
| 407 changeRouteSource: changeRouteSource, | |
| 408 closeDialog: closeDialog, | |
| 409 closeRoute: closeRoute, | |
| 410 joinRoute: joinRoute, | |
| 411 onInitialDataReceived: onInitialDataReceived, | |
| 412 reportBlur: reportBlur, | |
| 413 reportClickedSinkIndex: reportClickedSinkIndex, | |
| 414 reportFilter: reportFilter, | |
| 415 reportInitialAction: reportInitialAction, | |
| 416 reportInitialState: reportInitialState, | |
| 417 reportNavigateToView: reportNavigateToView, | |
| 418 reportRouteCreation: reportRouteCreation, | |
| 419 reportRouteCreationOutcome: reportRouteCreationOutcome, | |
| 420 reportSelectedCastMode: reportSelectedCastMode, | |
| 421 reportSinkCount: reportSinkCount, | |
| 422 reportTimeToClickSink: reportTimeToClickSink, | |
| 423 reportTimeToInitialActionClose: reportTimeToInitialActionClose, | |
| 424 requestInitialData: requestInitialData, | |
| 425 requestRoute: requestRoute, | |
| 426 searchSinksAndCreateRoute: searchSinksAndCreateRoute, | |
| 427 }; | |
| 428 }); | |
| OLD | NEW |