1// 2// App Roulette script 3 4var AppRouletteData = (typeof AppRouletteData == 'undefined' ? [] : AppRouletteData); 5var AppRoulettes = []; 6jQuery(function() { 7 8 // Setup all app roulettes 9 jQuery.each(AppRouletteData, function(i, data) { 10 11 // Create app roulette 12 var roulette = new AppRoulette(data); 13 14 // Store it 15 AppRoulettes.push(roulette); 16 17 }); 18 19}); 20 21AppRoulettes.doMomentum = function() { 22 23 // Get time delta 24 var lastTime = (AppRoulettes.lastAnimationTime ? AppRoulettes.lastAnimationTime : Date.now()); 25 var thisTime = Date.now(); 26 var delta = thisTime - lastTime; 27 AppRoulettes.lastAnimationTime = thisTime; 28 if (delta < 1) 29 delta = 1; 30 if (delta > 1000) 31 delta = 1000; 32 delta = delta / 1000; 33 34 // Do momentums 35 var inProgress = false; 36 for (var i = 0 ; i < AppRoulettes.length ; i++) { 37 38 if (AppRoulettes[i].doMomentum(delta)) 39 inProgress = true; 40 41 } 42 43 // If there are no current momentums, stop the loop 44 if (!inProgress) { 45 AppRoulettes.lastAnimationTime = 0; 46 return; 47 } 48 49 // Do again soon 50 if (typeof requestAnimationFrame == "undefined") { 51 52 // Use old animation method 53 setTimeout(AppRoulettes.doMomentum, 2); 54 55 } else { 56 57 // Use proper animation method 58 requestAnimationFrame(AppRoulettes.doMomentum); 59 60 } 61 62} 63 64 65 66// App Roulette class 67function AppRoulette(data) { 68 69 // Store data 70 this.data = data; 71 this.id = data.id; 72 this.apps = data.apps; 73 this.pluginURL = data.pluginURL; 74 75 // Store layout info 76 this.iconWidth = 128; 77 this.iconPadding = 20; 78 this.contentWidth = 0; 79 this.contentOffset = 0; 80 this.contentMomentum = 0; 81 this.contentOffsetTarget = -1; 82 this.containerWidth = 0; 83 this.lastTouchX = -1; 84 85 // Get div 86 this.div = jQuery("#approulette-" + this.id); 87 88 // Create icon container 89 this.iconContainer = jQuery("<div class='icons'></div>"); 90 this.div.append(this.iconContainer); 91 92 // Create icons 93 var _this = this; 94 jQuery.each(this.apps, function(i, app) { 95 96 // Create icon 97 app.icon = jQuery("<div class='icon' id='" + app.id + "'></div>"); 98 app.icon.css("background-image", "url('" + app.iconURL + "')"); 99 _this.iconContainer.append(app.icon); 100 101 }); 102 103 // Create divider 104 this.div.append("<div class='divider'></div>"); 105 106 // Create info area 107 this.infoDiv = jQuery("<div class='appinfos'></div>"); 108 this.div.append(this.infoDiv); 109 110 // Create info divs for each app 111 jQuery.each(this.apps, function(i, app) { 112 113 app.infoDiv = jQuery("<div class='appinfo'></div>"); 114 app.infoDiv.append("<p class='name'>" + app.name + "</p>"); 115 app.infoDiv.append("<p class='short-description'>" + app.shortDescription + "</p>"); 116 app.infoDiv.append("<div class='install-links'></div>"); 117 app.infoDiv.append("<div class='description'>" + app.description + "</div>"); 118 _this.infoDiv.append(app.infoDiv); 119 120 // Add iOS app store icon 121 if (app.iosAppStoreURL) 122 app.infoDiv.find(".install-links").append("<a target='_blank' href='" + app.iosAppStoreURL + "'><img src='" + _this.pluginURL + "/images/ios-app-store-link.png' class='app-store-link'/></a>"); 123 124 // Add Google Play app store icon 125 if (app.gplayAppStoreURL) 126 app.infoDiv.find(".install-links").append("<a target='_blank' href='" + app.gplayAppStoreURL + "'><img src='" + _this.pluginURL + "/images/gplay-app-store-link.png' class='app-store-link'/></a>"); 127 128 // Add Windows Phone app store icon 129 if (app.windowsPhoneURL) 130 app.infoDiv.find(".install-links").append("<a target='_blank' href='" + app.windowsPhoneURL + "'><img src='" + _this.pluginURL + "/images/windows-phone-link.png' class='app-store-link'/></a>"); 131 132 // Add Windows app store icon 133 if (app.windowsURL) 134 app.infoDiv.find(".install-links").append("<a target='_blank' href='" + app.windowsURL + "'><img src='" + _this.pluginURL + "/images/windows-link.png' class='app-store-link'/></a>"); 135 136 // Add Mac app store icon 137 if (app.macAppStoreURL) 138 app.infoDiv.find(".install-links").append("<a target='_blank' href='" + app.macAppStoreURL + "'><img src='" + _this.pluginURL + "/images/mac-app-store-link.png' class='app-store-link'/></a>"); 139 140 // Add Linux app store icon 141 if (app.linuxURL)
142 app.infoDiv.find(".install-links").append("<a target='_blank' href='" + app.linuxURL + "'><img src='" + _this.pluginURL + "/images/linux-link.png' class='app-store-link'/></a>"); 143 144 // Add Java app store icon 145 if (app.javaURL) 146 app.infoDiv.find(".install-links").append("<a target='_blank' href='" + app.javaURL + "'><img src='" + _this.pluginURL + "/images/java-link.png' class='app-store-link'/></a>"); 147 148 }); 149 150 // Register layout handler 151 jQuery(window).resize(function() { 152 _this.layout(); 153 }); 154 this.layout(); 155 156 // Register event handlers 157 this.iconContainer.mousedown(function(e) { 158 _this.onMouseDown(e); 159 }); 160 jQuery(document.body).mousemove(function(e) { 161 _this.onMouseMove(e); 162 }); 163 jQuery(document.body).mouseup(function(e) { 164 _this.onMouseUp(e); 165 }); 166 this.iconContainer.on("touchstart", function(e) { 167 _this.onTouchStart(e); 168 }); 169 this.iconContainer.on("touchmove", function(e) { 170 _this.onTouchMove(e); 171 }); 172 this.iconContainer.on("touchend", function(e) { 173 _this.onTouchEnd(e); 174 }); 175 176 // Show app info 177 this.showAppInfo(this.apps[0]); 178 179} 180 181AppRoulette.prototype.scrollToIndex = function(idx) { 182 183 // Check range 184 if (idx < 0) idx = 0; 185 if (idx >= this.apps.length) idx = this.apps.length - 1; 186 187 // Show app info immediately 188 this.showAppInfo(this.apps[idx]); 189 this.currentIdx = idx; 190 191 // Set target 192 this.contentOffsetTarget = idx * (this.iconWidth + this.iconPadding); 193 194 // Start updating frames again 195 AppRoulettes.doMomentum(); 196 197} 198 199AppRoulette.prototype.showAppInfo = function(app) { 200 201 // Hide all app infos 202 jQuery.each(this.apps, function(i, napp) { 203 204 if (napp.id == app.id) 205 napp.infoDiv.addClass("visible"); 206 else 207 napp.infoDiv.removeClass("visible"); 208 209 }); 210 211} 212 213AppRoulette.prototype.layout = function() { 214 215 // Store info 216 this.containerWidth = this.iconContainer.width(); 217 var smallMode = (this.containerWidth <= 450); 218 this.iconPadding = (smallMode ? 10 : 20); 219 this.iconWidth = (smallMode ? 64 : 128); 220 this.contentWidth = (this.iconWidth + this.iconPadding) * (this.apps.length - 1); 221 222 // Update icon sizes 223 if (smallMode) 224 this.div.addClass("small-mode"); 225 else 226 this.div.removeClass("small-mode"); 227 228 // Layout icons 229 this.layoutIcons(); 230 231} 232 233AppRoulette.prototype.layoutIcons = function() { 234 235 // Layout icons 236 var rangeFromCenter = this.containerWidth/2 - this.iconWidth/2; 237 var offset = this.containerWidth/2 - this.iconWidth/2 - this.contentOffset; 238 for (var i = 0 ; i < this.apps.length ; i++) { 239 240 // Get app 241 var app = this.apps[i]; 242 243 // Get distance from center, from 1 (close) to 0 (far) 244 var distance = offset - rangeFromCenter; 245 if (distance < 0) 246 distance = -distance; 247 if (distance > rangeFromCenter) 248 distance = rangeFromCenter; 249 distance = (rangeFromCenter - distance) / rangeFromCenter; 250 251 // Get distance from center, -1 far left, 0 close, 1 far right 252 var vectorDistance = offset - rangeFromCenter; 253 if (vectorDistance > rangeFromCenter) 254 vectorDistance = rangeFromCenter; 255 if (vectorDistance < -rangeFromCenter) 256 vectorDistance = -rangeFromCenter; 257 vectorDistance = vectorDistance / rangeFromCenter; 258 259 // Calculate position 260 var pos = offset;// - (1-distance) * 100; 261 262 // Move offset 263 offset += this.iconWidth + this.iconPadding; 264 //continue; 265 266 // Check distance 267 if (distance <= 0) { 268 app.icon.css("opacity", "0"); 269 app.icon.css("transform", "translateX(-1000px)"); 270 continue; 271 } 272 273 // Calculate rotation amount 274 var radius = rangeFromCenter; 275 var rotation = Math.sin(vectorDistance * Math.PI/2) * 30; 276 var scale = (Math.sin(distance * Math.PI/2)-1) * radius/3; 277 var opacity = Math.sin(distance * Math.PI/2) * 1; 278 app.icon.css("opacity", opacity); 279 app.icon.css("transform", "translateX(" + pos + "px) translateZ(" + scale + "px) rotateY(" + rotation + "deg)"); 280 281 } 282 283} 284 285AppRoulette.prototype.onMouseDown = function(e) { 286 287 // Cancel default event 288 e.preventDefault(); 289 290 // Start responding to events 291 this.lastTouchX = e.clientX; 292 this.contentMomentum = 0; 293 this.touchDidMove = false; 294 this.contentOffsetTarget = -1; 295 296} 297 298AppRoulette.prototype.onMouseMove = function(e) { 299 300 // Check if responding 301 if (this.lastTouchX < 0) 302 return; 303 304 // Cancel default event 305 e.preventDefault(); 306 307 // Check if beyond boundary 308 var beyondBoundary = (this.contentOffset < 0) || (this.contentOffset > this.contentWidth); 309 310 // Move content offset 311 var distance = (this.lastTouchX - e.clientX); 312 if (beyondBoundary) distance = distance / 3; 313 this.contentOffset += parseInt(distance); 314 this.layout(); 315 316 // Save offset 317 this.lastTouchX = e.clientX; 318 this.touchDidMove = true; 319 320 // Use momentum 321 this.lastTouchMomentum = distance; 322 this.lastTouchTime = Date.now(); 323 324} 325 326AppRoulette.prototype.onMouseUp = function(e) { 327 328 // Stop responding to events 329 this.lastTouchX = -1; 330 331 // Use momentum if is was quick 332 if (Date.now() - this.lastTouchTime < 100) 333 this.contentMomentum = this.lastTouchMomentum; 334 335 // Do momentum 336 AppRoulettes.doMomentum(); 337 338 // Do click
339 if (!this.touchDidMove) { 340 341 // Check which was clicked 342 var idx = -1; 343 for (var i = 0 ; i < this.apps.length ; i++) { 344 if (this.apps[i].id == e.target.id) { 345 idx = i; 346 break; 347 } 348 } 349 350 // Show app 351 if (idx >= 0) 352 this.scrollToIndex(idx); 353 354 } 355 356} 357 358AppRoulette.prototype.onTouchStart = function(e) { 359 360 // Check if existing event is in progress 361 if (this.lastTouchID) 362 return; 363 364 // Start responding to events 365 var evt = e.originalEvent.changedTouches[0]; 366 this.lastTouchX = evt.clientX; 367 this.lastTouchY = evt.clientY; 368 this.lastTouchID = evt.identifier; 369 this.contentMomentum = 0; 370 this.touchDidMove = false; 371 this.contentOffsetTarget = -1; 372 this.touchAccepted = false; 373 374} 375 376AppRoulette.prototype.onTouchMove = function(e) { 377 378 // Check if got touch ID 379 if (!this.lastTouchID) 380 return; 381 382 // Find event 383 var evt = null; 384 for (var i = 0 ; i < e.originalEvent.changedTouches.length ; i++) { 385 if (e.originalEvent.changedTouches[i].identifier == this.lastTouchID) { 386 evt = e.originalEvent.changedTouches[i]; 387 break; 388 } 389 } 390 391 if (!evt) 392 return; 393 394 // Check if touch is accepted 395 if (!this.touchAccepted) { 396 397 // Check movement direction 398 var mX = this.lastTouchX - evt.clientX; 399 var mY = this.lastTouchY - evt.clientY; 400 if (mX < 0) mX = -mX; 401 if (mY < 0) mY = -mY; 402 if (mX > mY) { 403 404 // Horizontal, accept this drag 405 this.touchAccepted = true; 406 e.preventDefault(); 407 408 } else { 409 410 // Vertical, user is scrolling page and just happened to be touching the app wheel. 411 // Cancel this drag 412 this.lastTouchX = -1; 413 this.lastTouchID = null; 414 return; 415 416 } 417 418 } 419 420 // Check if beyond boundary 421 var beyondBoundary = (this.contentOffset < 0) || (this.contentOffset > this.contentWidth); 422 423 // Move content offset 424 var distance = (this.lastTouchX - evt.clientX); 425 if (beyondBoundary) distance = distance / 3; 426 this.contentOffset += parseInt(distance); 427 this.layoutIcons(); 428 429 // Save offset 430 this.lastTouchX = evt.clientX; 431 this.touchDidMove = true; 432 433 // Use momentum 434 this.lastTouchMomentum = distance; 435 this.lastTouchTime = Date.now(); 436 437} 438 439AppRoulette.prototype.onTouchEnd = function(e) { 440 441 // Check if touch was cancelled 442 if (!this.lastTouchID) 443 return; 444 445 // Stop responding to events 446 this.lastTouchX = -1; 447 this.lastTouchID = null; 448 449 // Use momentum if is was quick 450 if (Date.now() - this.lastTouchTime < 100) 451 this.contentMomentum = this.lastTouchMomentum; 452 453 // Do momentum 454 AppRoulettes.doMomentum(); 455 456 // Do click 457 if (!this.touchDidMove) { 458 459 // Check which was clicked 460 var idx = -1; 461 for (var i = 0 ; i < this.apps.length ; i++) { 462 if (this.apps[i].id == e.target.id) { 463 idx = i; 464 break; 465 } 466 } 467 468 // Show app 469 if (idx >= 0) 470 this.scrollToIndex(idx); 471 472 } 473 474} 475 476AppRoulette.prototype.doMomentum = function(delta) { 477 478 // Check if busy with a touch 479 if (this.lastTouchX != -1) 480 return false; 481 482 // Check for momentum 483 if (this.contentMomentum == 0 && this.contentOffset >= -1 && this.contentOffset <= this.contentWidth + 1) { 484 485 // Stopped, check if got a target offset 486 if (this.contentOffsetTarget >= 0 && (this.contentOffset <= this.contentOffsetTarget-1 || this.contentOffset >= this.contentOffsetTarget+1)) { 487 488 // Move towards target position 489 this.contentOffset += (this.contentOffsetTarget - this.contentOffset) * delta * 8; 490 491 // Layout icons 492 this.layoutIcons(); 493 return true; 494 495 } else { 496 497 // Target reached 498 this.contentOffsetTarget = -1; 499 500 } 501 502 // Check if on an icon 503 var idx = parseInt((this.contentOffset + this.iconWidth/2) / (this.iconWidth + this.iconPadding)); 504 if (idx < 0) idx = 0; 505 if (idx >= this.apps.length) idx = this.apps.length-1; 506 var targetXpos = idx * (this.iconWidth + this.iconPadding); 507 if (this.contentOffset < targetXpos-2 || this.contentOffset > targetXpos+2) { 508 509 // Move towards target position 510 this.contentOffset += (targetXpos - this.contentOffset) * delta * 8; 511 512 // Layout icons 513 this.layoutIcons(); 514 return true; 515 516 } 517 518 // Check if new idx is different 519 if (this.currentIdx != idx) { 520 521 // Save index 522 this.currentIdx = idx; 523 524 // Show app info 525 this.showAppInfo(this.apps[idx]); 526 527 } 528 529 // Done 530 return false;
531 532 } 533 534 // Move content 535 this.contentOffset += parseInt(this.contentMomentum); 536 537 // Add friction 538 this.contentMomentum *= 0.95; 539 540 // Check if end of loop 541 if (this.contentMomentum < 1 && this.contentMomentum > -1) 542 this.contentMomentum = 0; 543 544 // Add more friction if outside of bounds 545 if (this.contentMomentum != 0) 546 if (this.contentOffset < 0 || this.contentOffset > this.contentWidth) 547 this.contentMomentum *= 0.8; 548 549 // Check if too far to the left 550 if (this.contentOffset < 0 && this.contentMomentum == 0) 551 this.contentOffset += -this.contentOffset * delta * 8; 552 553 // Check if too far to the right 554 if (this.contentOffset > this.contentWidth && this.contentMomentum == 0) 555 this.contentOffset -= (this.contentOffset - this.contentWidth) * delta * 8; 556 557 // Layout icons 558 this.layoutIcons(); 559 560 // Do again soon 561 return true; 562 563}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.