Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(132)

Side by Side Diff: chrome/browser/resources/ntp/apps.js

Issue 6297013: [NTP] Allow reordering of apps via drag and drop. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 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 var MAX_APPS_PER_ROW = []; 5 var MAX_APPS_PER_ROW = [];
6 MAX_APPS_PER_ROW[LayoutMode.SMALL] = 4; 6 MAX_APPS_PER_ROW[LayoutMode.SMALL] = 4;
7 MAX_APPS_PER_ROW[LayoutMode.NORMAL] = 6; 7 MAX_APPS_PER_ROW[LayoutMode.NORMAL] = 6;
8 8
9 // The URL prefix used in the app link 'ping' attributes. 9 // The URL prefix used in the app link 'ping' attributes.
10 var PING_APP_LAUNCH_PREFIX = 'record-app-launch'; 10 var PING_APP_LAUNCH_PREFIX = 'record-app-launch';
(...skipping 27 matching lines...) Expand all
38 $('apps-create-shortcut-command-separator').style.display = 38 $('apps-create-shortcut-command-separator').style.display =
39 (data.disableCreateAppShortcut ? 'none' : ''); 39 (data.disableCreateAppShortcut ? 'none' : '');
40 40
41 appsMiniview.textContent = ''; 41 appsMiniview.textContent = '';
42 appsSectionContent.textContent = ''; 42 appsSectionContent.textContent = '';
43 43
44 data.apps.sort(function(a,b) { 44 data.apps.sort(function(a,b) {
45 return a.app_launch_index - b.app_launch_index; 45 return a.app_launch_index - b.app_launch_index;
46 }); 46 });
47 47
48 // Determines if the web store link should be detached and place in the
49 // top right of the screen.
50 apps.detachWebstoreEntry =
51 !apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode];
52
53 apps.data = data.apps;
54 if (!apps.detachWebstoreEntry)
55 apps.data.push('web-store-entry');
56
48 clearClosedMenu(apps.menu); 57 clearClosedMenu(apps.menu);
49 data.apps.forEach(function(app) { 58 data.apps.forEach(function(app) {
50 appsSectionContent.appendChild(apps.createElement(app)); 59 appsSectionContent.appendChild(apps.createElement(app));
51 }); 60 });
52 61
53 webStoreEntry = apps.createWebStoreElement(); 62 webStoreEntry = apps.createWebStoreElement();
54 webStoreEntry.querySelector('a').setAttribute('ping', appsPromoPing); 63 webStoreEntry.querySelector('a').setAttribute('ping', appsPromoPing);
55 appsSectionContent.appendChild(webStoreEntry); 64 appsSectionContent.appendChild(webStoreEntry);
56 65
57 data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) { 66 data.apps.slice(0, MAX_MINIVIEW_ITEMS).forEach(function(app) {
(...skipping 19 matching lines...) Expand all
77 if (apps.showPromo) 86 if (apps.showPromo)
78 document.documentElement.classList.add('apps-promo-visible'); 87 document.documentElement.classList.add('apps-promo-visible');
79 else 88 else
80 document.documentElement.classList.remove('apps-promo-visible'); 89 document.documentElement.classList.remove('apps-promo-visible');
81 90
82 var appsPromoLink = $('apps-promo-link'); 91 var appsPromoLink = $('apps-promo-link');
83 if (appsPromoLink) 92 if (appsPromoLink)
84 appsPromoLink.setAttribute('ping', appsPromoPing); 93 appsPromoLink.setAttribute('ping', appsPromoPing);
85 maybeDoneLoading(); 94 maybeDoneLoading();
86 95
96 // Disable the animations when the app launcher is being (re)initailized.
97 apps.layout(true);
98
99 if (apps.detachWebstoreEntry)
100 webStoreEntry.classList.add('loner');
101 else
102 webStoreEntry.classList.remove('loner');
103
87 if (isDoneLoading()) { 104 if (isDoneLoading()) {
88 if (!apps.showPromo && data.apps.length >= MAX_APPS_PER_ROW[layoutMode])
89 webStoreEntry.classList.add('loner');
90 else
91 webStoreEntry.classList.remove('loner');
92
93 updateMiniviewClipping(appsMiniview); 105 updateMiniviewClipping(appsMiniview);
94 layoutSections(); 106 layoutSections();
95 } 107 }
96 } 108 }
97 109
98 function appsPrefChangeCallback(data) { 110 function appsPrefChangeCallback(data) {
99 // Currently the only pref that is watched is the launch type. 111 // Currently the only pref that is watched is the launch type.
100 data.apps.forEach(function(app) { 112 data.apps.forEach(function(app) {
101 var appLink = document.querySelector('.app a[app-id=' + app['id'] + ']'); 113 var appLink = document.querySelector('.app a[app-id=' + app['id'] + ']');
102 if (appLink) 114 if (appLink)
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after
254 case 'apps-options-command': 266 case 'apps-options-command':
255 e.canExecute = currentApp && currentApp['options_url']; 267 e.canExecute = currentApp && currentApp['options_url'];
256 break; 268 break;
257 case 'apps-launch-command': 269 case 'apps-launch-command':
258 case 'apps-uninstall-command': 270 case 'apps-uninstall-command':
259 e.canExecute = true; 271 e.canExecute = true;
260 break; 272 break;
261 } 273 }
262 }); 274 });
263 275
276 // Moves the element at position |from| in array |arr| to position |to|.
277 function arrayMove(arr, from, to) {
278 var element = arr.splice(from, 1);
279 arr.splice(to, 0, element[0]);
280 }
281
264 return { 282 return {
265 loaded: false, 283 loaded: false,
266 284
267 menu: $('apps-menu'), 285 menu: $('apps-menu'),
268 286
269 showPromo: false, 287 showPromo: false,
270 288
289 detachWebstoreEntry: false,
290
291 // The list of app ids, in order, of each app in the launcher.
292 data_: null,
293 get data() { return this.data_; },
294 set data(data) {
295 var ids = [];
arv (Not doing code reviews) 2011/01/22 00:52:14 this.data_ = data.map(function(app) { return app
jstritar 2011/01/24 01:00:42 Done.
296 data.forEach(function(app) {
297 ids.push(app.id);
298 });
299 this.data_ = ids;
300 this.invalidate();
301 },
302
303 dirty_: true,
304 invalidate: function() {
305 this.dirty_ = true;
306 },
307
308 visible_: true,
309 get visible() {
310 return this.visible_;
311 },
312 set visible(visible) {
313 this.visible_ = visible;
314 this.invalidate();
315 },
316
317 // DragAndDropDelegate
318
319 dragContainer: $('apps-content'),
320 transitionsDuration: 200,
321
322 get dragItem() { return this.dragItem_; },
323 set dragItem(dragItem) {
324 if (this.dragItem_ != dragItem) {
325 this.dragItem_ = dragItem;
326 this.invalidate();
327 }
328 },
329
330 // These are the dimensions of each item in the app launcher. These need
331 // to be in sync with the rules in apps.css.
Aaron Boodman 2011/01/22 23:42:57 Is it possible to get them via offsetHeight/offset
jstritar 2011/01/24 01:00:42 Done.
332 get dimensions() {
333 return {
334 itemHeight: 136,
335 itemWidth: 124,
336 marginWidth: 3,
337 marginHeight: 5,
338 borderWidth: 0
339 };
340 },
341
342 // Gets the item under the mouse event |e|. Returns null if there is no
343 // item or if the item is not draggable.
344 getItem: function(e) {
345 var item = findAncestorByClass(e.target, 'app');
346
347 // You can't drag the web store launcher.
348 if (item.classList.contains('web-store-entry'))
349 return null;
350
351 return item;
352 },
353
354 // Returns true if |position| is a valid place to drop an app.
355 canDropOn: function(position) {
356 var appCount = this.data.length;
357 if (!this.detachWebstoreEntry)
358 appCount--;
359 return position >= 0 && position < appCount;
360 },
361
362 setDragPlaceholder: function(position) {
363 var appId = this.dragItem.querySelector('a').getAttribute('app-id');
364 var current = this.data.indexOf(appId);
365
366 if (current == position || current < 0)
367 return;
368
369 arrayMove(this.data, current, position);
370 this.invalidate();
371 this.layout();
372 },
373
374 saveDrag: function() {
Aaron Boodman 2011/01/22 23:42:57 Method isn't called from dragdrop.js ?
jstritar 2011/01/24 01:00:42 Oops.... that could be a problem! Done.
375 this.invalidate();
376 this.layout();
377
378 // Wait until the transitions are complete before notifying the browser.
379 // Otherwise, the apps will be re-rendered while still transitioning.
380 setTimeout(function() {
381 chrome.send('reorderApps', this.data.filter(function(id) {
382 return id != 'web-store-entry';
383 }));
384 }, this.transitionsDuration + 10);
385 },
386
387 layout: function(disable_animations) {
Aaron Boodman 2011/01/22 23:42:57 It sucks that it is different, but naming in JavaS
Aaron Boodman 2011/01/22 23:42:57 Style nit: I don't much care for the boolean flags
jstritar 2011/01/24 01:00:42 Done. Went with layout({disableAnimation:true}) in
jstritar 2011/01/24 01:00:42 Done.
388 if (!this.dirty_)
389 return;
390
391 try {
392 if (disable_animations)
393 this.dragContainer.setAttribute('launcher-animations', false);
394 var d0 = Date.now();
395 this.applyAppRects();
396 this.dirty_ = false;
397 logEvent('apps.layout: ' + (Date.now() - d0));
398
399 } finally {
400 if (disable_animations) {
401 // We need to re-enable animations asynchronously, so that the
402 // animations are still disabled for this layout update.
403 setTimeout(function() {
404 this.dragContainer.setAttribute('launcher-animations', true);
arv (Not doing code reviews) 2011/01/22 00:52:14 How about introducing a variable to dragContainer.
jstritar 2011/01/24 01:00:42 Done.
405 }.bind(this), 0);
406 }
407 }
408 },
409
410 applyAppRects: function() {
Aaron Boodman 2011/01/22 23:42:57 Kind of an odd name for this method. What about la
jstritar 2011/01/24 01:00:42 Done.
411 var apps = this.data;
412 var rects = this.getAppLayoutRects(apps.length);
413 var appsContent = $('apps-content');
414 var d = this.dimensions;
415 var h = d.itemHeight + 2 * d.marginHeight + 2 * d.borderWidth;
416 var maxRows = 0;
417
418 for (var i = 0; i < apps.length; i++) {
419 var t = appsContent.querySelector('[app-id='+apps[i]+']').parentNode;
Aaron Boodman 2011/01/22 23:42:57 Avoid single variable letter names unless they are
jstritar 2011/01/24 01:00:42 Done.
420
421 // If the node is being dragged, don't try to place it in the grid.
422 if (t == this.dragItem)
423 continue;
424
425 maxRows = Math.max(maxRows, rects[i].row);
426
427 t.style.left = rects[i].left + 'px';
428 t.style.top = rects[i].top + 'px';
429 t.style.right = '';
Aaron Boodman 2011/01/22 23:42:57 It looks like maybe you were going to use 'right',
jstritar 2011/01/24 01:00:42 Done.
430 t.style.display = this.visible ? '' : 'none';
Aaron Boodman 2011/01/22 23:42:57 If this.visible is false, does it mean that the se
jstritar 2011/01/24 01:00:42 Done.
431
432 var innerStyle = t.firstElementChild.style;
433 innerStyle.left = innerStyle.top = '';
Aaron Boodman 2011/01/22 23:42:57 This seems odd, when are these ever used?
jstritar 2011/01/24 01:00:42 Oops... must have brought this in from the most vi
434 }
435
436 // Set the container's height manually now that we use absolute
437 // positioning.
438 var lastRect = rects[rects.length - 1];
439 appsContent.style.height = (++maxRows * h) + 'px';
440 },
441
442 getAppLayoutRects: function(appCount) {
443 var d = this.dimensions;
Aaron Boodman 2011/01/22 23:42:57 I don't think it's obvious what d stands for. How
jstritar 2011/01/24 01:00:42 Done.
444 var availableWidth = this.dragContainer.offsetWidth;
445 var rtl = isRtl();
446 var rects = [];
447 var w = d.itemWidth + 2 * d.borderWidth + 2 * d.marginWidth;
448 var h = d.itemHeight + 2 * d.borderWidth + 2 * d.marginHeight;
449
450 for (var i = 0; i < appCount; i++) {
451 var row = Math.floor((w * i) / availableWidth);
452 var top = row * h;
453 var left = (w * i) % availableWidth;
454
455 // Reflect the X axis if an RTL language is active.
456 if (rtl) left = availableWidth - left - w;
arv (Not doing code reviews) 2011/01/22 00:52:14 line break?
jstritar 2011/01/24 01:00:42 Done.
457 rects[i] = {left: left, top: top, row: row};
458 }
459 return rects;
460 },
461
271 createElement: function(app) { 462 createElement: function(app) {
272 var div = createElement(app); 463 var div = createElement(app);
273 var a = div.firstChild; 464 var a = div.firstChild;
274 465
275 a.onclick = handleClick; 466 a.onclick = handleClick;
276 a.setAttribute('ping', PING_APP_LAUNCH_PREFIX + '+' + this.showPromo); 467 a.setAttribute('ping', PING_APP_LAUNCH_PREFIX + '+' + this.showPromo);
277 a.style.backgroundImage = url(app['icon_big']); 468 a.style.backgroundImage = url(app['icon_big']);
278 if (hashParams['app-id'] == app['id']) { 469 if (hashParams['app-id'] == app['id']) {
279 div.setAttribute('new', 'new'); 470 div.setAttribute('new', 'new');
280 // Delay changing the attribute a bit to let the page settle down a bit. 471 // Delay changing the attribute a bit to let the page settle down a bit.
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
337 528
338 return a; 529 return a;
339 }, 530 },
340 531
341 createWebStoreElement: function() { 532 createWebStoreElement: function() {
342 var elm = createElement({ 533 var elm = createElement({
343 'id': 'web-store-entry', 534 'id': 'web-store-entry',
344 'name': localStrings.getString('web_store_title'), 535 'name': localStrings.getString('web_store_title'),
345 'launch_url': localStrings.getString('web_store_url') 536 'launch_url': localStrings.getString('web_store_url')
346 }); 537 });
347 elm.setAttribute('app-id', 'web-store-entry'); 538 elm.classList.add('web-store-entry');
348 return elm; 539 return elm;
349 }, 540 },
350 541
351 createWebStoreMiniElement: function() { 542 createWebStoreMiniElement: function() {
352 var span = document.createElement('span'); 543 var span = document.createElement('span');
353 span.appendChild(this.createWebStoreClosedMenuElement()); 544 span.appendChild(this.createWebStoreClosedMenuElement());
354 return span; 545 return span;
355 }, 546 },
356 547
357 createWebStoreClosedMenuElement: function() { 548 createWebStoreClosedMenuElement: function() {
358 var a = document.createElement('a'); 549 var a = document.createElement('a');
359 a.textContent = localStrings.getString('web_store_title'); 550 a.textContent = localStrings.getString('web_store_title');
360 a.href = localStrings.getString('web_store_url'); 551 a.href = localStrings.getString('web_store_url');
361 a.style.backgroundImage = url('chrome://theme/IDR_PRODUCT_LOGO_16'); 552 a.style.backgroundImage = url('chrome://theme/IDR_PRODUCT_LOGO_16');
362 a.className = 'item'; 553 a.className = 'item';
363 return a; 554 return a;
364 } 555 }
365 }; 556 };
366 })(); 557 })();
558
559 // Enable drag and drop reordering of the app launcher.
560 var appDragAndDrop = new DragAndDropController(apps);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698