1
0

sl-application.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. /**
  2. * Namespace maker
  3. *
  4. * by Ringo
  5. *
  6. * A very simple way to create a jquery namespace to help organize related
  7. * methods, constants, etc.
  8. */
  9. if (typeof($) === 'undefined') {
  10. $ = {};
  11. }
  12. /**
  13. * Define and construct the jquery namespace
  14. *
  15. * @return {null}
  16. */
  17. $.namespace = function() {
  18. var o = null;
  19. var i, j, d;
  20. for (i = 0; i < arguments.length; i++) {
  21. d = arguments[i].split(".");
  22. o = window;
  23. for (j = 0; j < d.length; j++) {
  24. o[ d[ j ] ] = o[ d[ j ] ] || {};
  25. o = o[ d[ j ] ];
  26. }
  27. }
  28. return o;
  29. };
  30. // Namespace declarations
  31. // Add new declarations here!
  32. $.namespace('$.sl.maps.config');
  33. $.namespace('$.sl.maps');
  34. $.sl.maps.config = {
  35. sl_base_url: "https://secondlife.com/",
  36. // tile_url: "https://secondlife-maps-cdn.akamaized.net",
  37. tile_url: "http://opensim.betatechnologies.info:8002",
  38. //Default Destination Information
  39. default_title: "Welcome to Second Life",
  40. default_img: "https://secondlife-maps-lecs.akamaized.net/agni/default-new.jpg",
  41. default_msg: "Second Life is a popular virtual space for meeting friends, doing business, and sharing knowledge. If you have Second Life installed on your computer, teleport in and start exploring!",
  42. // Turn on the map debugger
  43. map_debug: false,
  44. // The maximum width/height of the SL grid in regions:
  45. // 2^20 regions on a side = 1,048,786 ("This should be enough for anyone")
  46. // *NOTE: This must be a power of 2 and divisible by 2^(max zoom) = 256
  47. map_grid_edge_size: 1048576,
  48. // NOTE: Mustachejs Templates
  49. // These templates were moved in to here so they are available as soon as the javascript is loaded
  50. // that way we don't have to wait for the page to full load before processing all the data we have on hand.
  51. // Ballooon Popup Template
  52. balloon_tmpl: ' \
  53. <div class="balloon-content"> \
  54. <h3> \
  55. {{#slurl}} \
  56. <a href="{{slurl}}" onclick="trakkit(\'maps\', \'teleport\', \'{{slurl}}\');"> \
  57. {{/slurl}} \
  58. {{title}} \
  59. {{#slurl}} \
  60. </a> \
  61. {{/slurl}} \
  62. </h3> \
  63. {{#img}} \
  64. <a href="{{slurl}}" onclick="trakkit(\'maps\', \'teleport\', \'{{slurl}}\');"> \
  65. <img src="{{img}}" onError="this.onerror=null;this.src=assetsURL + \'default-new.jpg\';" /> \
  66. </a> \
  67. {{/img}} \
  68. <p>{{msg}}</p> \
  69. <div class="buttons"> \
  70. {{#slurl}} \
  71. <a class="HIGHLANDER_button_hot btn_large primary" title="visit this location" href="{{slurl}}" onclick="trakkit(\'maps\', \'teleport\', \'{{slurl}}\');">Visit this location</a> \
  72. {{/slurl}} \
  73. <a href="https://join.secondlife.com/" target="_top" class="HIGHLANDER_button_hot btn_large secondary join_button">Join Now, it&rsquo;s free!</a> \
  74. </div> \
  75. </div>',
  76. // Slurl Doesn't Exist Templte
  77. noexists_tmpl: ' \
  78. <div id="map-error"> \
  79. <div id="error-content"> \
  80. <span class="error-close">Hide message</span> \
  81. <span class="location-title">We are unable to locate the region "{{region_name}}"</span> \
  82. <p>This region may no longer exist, but please double check your spelling and coordinates to make sure there aren&rsquo;t any errors and try again.</p> \
  83. <p>If your problem persists, contact <a href="http://secondlife.com/support/">Second Life support</a></p> \
  84. </div> \
  85. </div>'
  86. }
  87. ;
  88. // License and Terms of Use
  89. //
  90. // Copyright 2016 Linden Research, Inc.
  91. //
  92. // Permission is hereby granted, free of charge, to any person obtaining a copy
  93. // of this software and associated documentation files (the "Software"), to deal
  94. // in the Software without restriction, including without limitation the rights
  95. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  96. // copies of the Software, and to permit persons to whom the Software is
  97. // furnished to do so, subject to the following conditions:
  98. //
  99. // The above copyright notice and this permission notice shall be included in
  100. // all copies or substantial portions of the Software.
  101. //
  102. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  103. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  104. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  105. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  106. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  107. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  108. // THE SOFTWARE.
  109. //
  110. // This javascript makes use of the Second Life Map API, which is documented
  111. // at http://wiki.secondlife.com/wiki/Map_API
  112. //
  113. // Use of the Second Life Map API is subject to the Second Life API Terms of Use:
  114. // https://wiki.secondlife.com/wiki/Linden_Lab_Official:API_Terms_of_Use
  115. //
  116. // Questions regarding this javascript, and any suggested improvements to it,
  117. // should be sent to the mailing list [email protected]
  118. // ==============================================================================
  119. /*
  120. # How A Leaflet Map #
  121. Unlike Google Maps, Leaflet provides a "Simple" coordinate space for 2D maps
  122. like Second Life's out of the box. The Simple CRS is not even limited by a
  123. geographic conception of coordinate limits: we can really use the real
  124. regionspace coordinates & the Simple CRS operates correctly. This means we have
  125. nonsensical (Earth-wise) "latitudes" & "longitudes", because they're really
  126. just coordinates.
  127. <http://leafletjs.com/examples/crs-simple/crs-simple.html>
  128. For further simplicity, the possible Second Life region space (labeled here
  129. "grid_x, grid_y") is mapped to the upper right quadrant of such a map. This
  130. puts the (0, 0) origin of region space at LatLng (0, 0).
  131. (0, 2^20) long, lat
  132. (0, 2^20) grid_x, grid_y
  133. |
  134. V
  135. X------------------------------------+
  136. | |
  137. | |
  138. | |
  139. | |
  140. | |
  141. | |
  142. | |
  143. | |
  144. | xxx |
  145. | xxx | (2^20, 0) long, lat
  146. X------------------------------------X<- (2^20, 0) grid_x, grid_y
  147. ^
  148. |
  149. (0, 0) long, lat
  150. (0, 0) grid_x, grid_y
  151. A large scaling value called `map_grid_edge_size` defines the largest region
  152. coordinate at the top and far right edge of the map. At the current value of
  153. 2^20 = 1M, this creates a map area with room for 1 trillion regions. The xxx'd
  154. area of the map represents today's populated regions.
  155. Previously, Google Maps API v3 mapped maps into a 0-256 "world coordinate" box,
  156. so we just used that, putting the 0 edge of regionspace at -180 longitude.
  157. Google Maps API v2 before it still used lat/lng as Leaflet does, so we are
  158. returned to mapping the SL regionspace to one theoretical quadrant of the
  159. mapspace to avoid negative coordinates.
  160. ## Zoom levels ##
  161. SL Maps zoom levels (Zl) start zoomed in, at Zl 1, where each tile comprises 1
  162. region by itself. At Zl 2 each tile is 2x2 regions, and doubled again (in each
  163. dimension) at each additional level. This means each tile is 2^(Zl-1) regions
  164. across.
  165. Leaflet lets us just use these zoom levels, though we have to specify our
  166. levels are backwards & start at 1. The wrinkle with Leaflet is it wants to
  167. address the tile images at different zoom levels in how many _tiles_ from (0,
  168. 0) we are, but SL map tiles are addressed in regionspace coordinates at all
  169. zoom levels. These are the same at Zl 1 where 1 tile = 1 region. We provide the
  170. `SLTileLayer` layer class to un-convert Leaflet's tile coordinates back to
  171. regionspace coordinates at the other zoom levels.
  172. Zoom levels in the Google Maps API were not only backwards, but organized such
  173. that at Google Zoom (Zg) level 0 the entire worldspace was one (square) tile.
  174. We needed a complex scheme for converting these at other steps too.
  175. */
  176. // === Constants ===
  177. var slDebugMap = $.sl.maps.config.map_debug;
  178. var MIN_ZOOM_LEVEL = 1;
  179. var MAX_ZOOM_LEVEL = 8;
  180. /**
  181. * Creates a Second Life map in the given DOM element & returns the Leaflet Map.
  182. *
  183. * @param {Element} map_element the DOM element to contain the map
  184. */
  185. function SLMap(mapElement, mapOptions)
  186. {
  187. mapElement.className += ' slmapapi2-map-container';
  188. var mapDiv = document.createElement("div");
  189. mapDiv.style.height = "100%";
  190. mapElement.appendChild(mapDiv);
  191. var SLTileLayer = L.TileLayer.extend({
  192. getTileUrl: function (coords) {
  193. var data = {
  194. r: L.Browser.retina ? '@2x' : '',
  195. s: this._getSubdomain(coords),
  196. z: this._getZoomForUrl()
  197. };
  198. var regionsPerTileEdge = Math.pow(2, data['z'] - 1);
  199. data['region_x'] = coords.x * regionsPerTileEdge;
  200. data['region_y'] = (Math.abs(coords.y) - 1) * regionsPerTileEdge;
  201. return L.Util.template(this._url, L.extend(data, this.options));
  202. }
  203. });
  204. var tiles = new SLTileLayer($.sl.maps.config.tile_url + "/map-{z}-{region_x}-{region_y}-objects.jpg", {
  205. crs: L.CRS.Simple,
  206. minZoom: MIN_ZOOM_LEVEL,
  207. maxZoom: MAX_ZOOM_LEVEL,
  208. zoomOffset: 1,
  209. zoomReverse: true,
  210. bounds: [[0, 0], [$.sl.maps.config.map_grid_edge_size, $.sl.maps.config.map_grid_edge_size]],
  211. attribution: "<a href='" + $.sl.maps.config.sl_base_url + "'>Second Life</a>"
  212. });
  213. var map = L.map(mapDiv, {
  214. crs: L.CRS.Simple,
  215. minZoom: MIN_ZOOM_LEVEL,
  216. maxZoom: MAX_ZOOM_LEVEL,
  217. maxBounds: [[0, 0], [$.sl.maps.config.map_grid_edge_size, $.sl.maps.config.map_grid_edge_size]],
  218. layers: [tiles]
  219. });
  220. map.on('click', function (event) {
  221. gotoSLURL(event.latlng.lng, event.latlng.lat, map);
  222. });
  223. return map;
  224. }
  225. /**
  226. * Loads the script with the given URL by adding a script tag to the document.
  227. *
  228. * @private
  229. * @param {string} scriptURL the script to load
  230. * @param {function} onLoadHandler a callback to call when the script is loaded (optional)
  231. */
  232. function slAddDynamicScript(scriptURL, onLoadHandler)
  233. {
  234. var script = document.createElement('script');
  235. script.src = scriptURL;
  236. script.type = "text/javascript";
  237. if (onLoadHandler) {
  238. // Need to use ready state change for IE as it doesn't support onload for scripts
  239. script.onreadystatechange = function () {
  240. if (script.readyState == 'complete' || script.readyState == 'loaded') {
  241. onLoadHandler();
  242. }
  243. }
  244. // Standard onload for Firefox/Safari/Opera etc
  245. script.onload = onLoadHandler;
  246. }
  247. document.body.appendChild(script);
  248. }
  249. /**
  250. * Opens a map window (info window) for the given SL location, giving its name
  251. * and a "Teleport Here" button, as when clicked.
  252. *
  253. * @param {number} x the horizontal (west-east) SL Maps region coordinate to open the map window at
  254. * @param {number} y the vertical (south-north) SL Maps region coordinate to open the map window at
  255. * @param {SLMap} slMap the map in which to open the map window
  256. */
  257. function gotoSLURL(x, y, lmap)
  258. {
  259. // Work out region co-ords, and local co-ords within region
  260. var int_x = Math.floor(x);
  261. var int_y = Math.floor(y);
  262. var local_x = Math.round((x - int_x) * 256);
  263. var local_y = Math.round((y - int_y) * 256);
  264. // Add a dynamic script to get this region name, and then trigger a URL change
  265. // based on the results
  266. var scriptURL = "https://cap.secondlife.com/cap/0/b713fe80-283b-4585-af4d-a3b7d9a32492"
  267. + "?var=slRegionName&grid_x=" + int_x + "&grid_y="+ int_y;
  268. // Once the script has loaded, we use the result to teleport the user into SL
  269. var onLoadHandler = function () {
  270. if (slRegionName == null || slRegionName.error)
  271. return;
  272. var url = "secondlife://" + encodeURIComponent(slRegionName)
  273. + "/" + local_x + "/" + local_y;
  274. var debugInfo = '';
  275. if (slDebugMap) {
  276. debugInfo = ' x: ' + int_x + ' y: ' + int_y;
  277. }
  278. var content = '<div class="balloon-content balloon-content-narrow"><h3><a href="' + url + '">'+ slRegionName + '</a></h3>'
  279. + debugInfo
  280. + '<div class="buttons"><a href="'+ url +'" class="HIGHLANDER_button_hot btn_large primary">Visit this location</a></div></div>';
  281. var popup = L.popup().setLatLng([y, x]).setContent(content).openOn(lmap);
  282. };
  283. slAddDynamicScript(scriptURL, onLoadHandler);
  284. }
  285. ;
  286. /* ============================
  287. ** Build a SLurl validation
  288. ** ============================
  289. ** Makes sure there are slurl coordinates input before we attempt to generate a SLurl */
  290. function slurlBuildValidator()
  291. {
  292. $('input#generate-slurl').click(function(){
  293. $('#build-location div.slurl-error').remove(); //remove the error message if there is one
  294. var intX = parseFloat($('#x').val());
  295. var intY = parseFloat($('#y').val());
  296. var intZ = parseFloat($('#z').val());
  297. var imgRegExp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
  298. if($('#windowImage').val()) {
  299. var imgCheck = $('#windowImage').val().search(imgRegExp);
  300. }
  301. else {
  302. var imgCheck = 0;
  303. }
  304. if(!$('#region').val()
  305. || ( isNaN($('#x').val()) || isNaN(intX) || intX<0 || intX>256 )
  306. || ( isNaN($('#y').val()) || isNaN(intY) || intY<0 || intY>256 )
  307. || ( isNaN($('#z').val()) || isNaN(intZ) )
  308. || ( $('#windowImage').val() != '' && imgCheck == -1 )
  309. ) {
  310. var error_content = "";
  311. if( !$('#region').val() ) {
  312. error_content += "<p>A valid SLurl must contain a region name and x,y,z coordinates. Please make sure that each of these fields are properly filled in.</p>";
  313. }
  314. if( isNaN($('#x').val()) || isNaN($('#y').val()) || isNaN($('#z').val()) || isNaN(intX) || isNaN(intY) || isNaN(intZ) || intX > 256 || intY > 256) {
  315. error_content += "<p>The x and y coordinates must each contain numbers between 0 and 256 to be valid. z index must be between -99 and 999. All coordinates (x,y,z) must be numeric.</p>";
  316. }
  317. if( intX<0 || intY<0) {
  318. error_content += "<p>The x and y coordinates must be positive numbers.</p>";
  319. }
  320. if(imgCheck == -1) {
  321. error_content += "<p>Window Image must be a fully qualified url to a hosted image.";
  322. error_content += "( <strong>Example: </strong><em>http://www.yoursite.com/images/yourImage.jpg</em> )</p>";
  323. }
  324. $('#build-location legend').after('<div class="slurl-error"><h4>We\'re having trouble creating your SLurl</h4>'+ error_content +'</div>');
  325. document.getElementById('slurl-builder').scrollTop=0;
  326. } else {
  327. build_url();
  328. }
  329. })
  330. }
  331. /* ============================
  332. ** Build a SLurl
  333. ** ============================
  334. ** Creates the SLurl */
  335. function build_url()
  336. {
  337. var slurl = $('#slurl_base').val() + escape($('#region').val()) + "/" + parseFloat($('input#x').val()) + "/" + parseFloat($('input#y').val()) + "/" + parseFloat($('input#z').val()) + "/";
  338. $('#slurl-builder form #return-slurl').css({'display' : 'block'});
  339. document.getElementById('slurl-builder').scrollTop = $('#slurl-builder').height() + $('#return-slurl').height();
  340. // return the slurl to the output field
  341. $('#output').val(slurl);
  342. // fade out a color animation - requires color animation plugin
  343. $('#return-slurl').animate({ backgroundColor: '#f8f8f8' }, 2500);
  344. }
  345. /* =====================
  346. ** Popup windows
  347. ** =====================
  348. ** Creates an unobtrusive popup window */
  349. function popUpExample()
  350. {
  351. $('a.popup').click(function(){
  352. var href = $(this).attr('href');
  353. var width = screen.width - 760;
  354. var height = screen.height - 550;
  355. var left = (screen.width - width)/2;
  356. var top = (screen.height - height)/2;
  357. window.open(href, 'popup', 'height='+ height +',width='+ width +',left='+ left +',top='+ top +',toolbar=no,scrollbars=yes');
  358. return false;
  359. });
  360. }
  361. ;
  362. // Find a url param by name
  363. function gup( name,url )
  364. {
  365. var regexS = "[\\?&]"+name+"=([^&#]*)";
  366. var regex = new RegExp( regexS );
  367. var tmpURL = (url === undefined) ? window.location.href : url;
  368. var results = regex.exec( tmpURL );
  369. if( results == null )
  370. return "";
  371. else
  372. return results[1];
  373. }
  374. function mapExtensions()
  375. {
  376. // New jquery styling for drop-down menus
  377. //$('#search_select').dropp();
  378. // Open & close the sidebar.
  379. $('#map-search-results').click(function (evt) {
  380. var isCollapsed = $(this).hasClass('collapsed');
  381. var collapserClicked = $(evt.target).parents().addBack().filter('#collapse-new').length;
  382. if (!isCollapsed && !collapserClicked) {
  383. return;
  384. }
  385. $(this).toggleClass('collapsed');
  386. $('#map-container').toggleClass('map-search-opened');
  387. });
  388. }
  389. //For Google Analytics - creates a custom trackable item
  390. function trakkit(category, action, opt_label, opt_value) {
  391. try { // depends on google analytics
  392. _gaq.push(['_trackEvent', category, action, opt_label]);
  393. //console.log('_trackEvent: '+category+','+action+','+opt_label);
  394. }
  395. catch(e) {
  396. //console.log('_trackEvent FAIL: '+e);
  397. }
  398. }
  399. /* ==============================================
  400. ** directSLurl
  401. ** ==============================================
  402. ** get a directSlurl on click of a login link */
  403. function directSlurl($self, location_string)
  404. {
  405. $.ajax({
  406. url: "/direct_slurl.php",
  407. type: "GET",
  408. data: { region_location_string: location_string },
  409. async: false,
  410. success: function(data) {
  411. if (data) $self.attr("href", data);
  412. }
  413. });
  414. }
  415. /* =========================================================================
  416. ** loadquery
  417. ** =========================================================================
  418. ** ajaxish loading of search and showcase results,
  419. ** so we can load the map first */
  420. function loadquery(doc, params)
  421. {
  422. var error_response = "<div class=\"notice\" style=\"margin:60px 15px;\""
  423. +"<h3>We've encountered a problem</h3>"
  424. +"<p>We were unable to complete the search for the content you requested."
  425. +" Please continue exploring the map while we look into the cause. Thank you for your patience!</p>"
  426. +"</div>";
  427. var leftJustifyTitle = false;
  428. var ajaxParams = (leftJustifyTitle) ? "null" : params;
  429. $.ajax({
  430. url: doc,
  431. type: 'GET',
  432. data: ajaxParams,
  433. timeout: 10000,
  434. error: function(){
  435. $('#map-search-results .loader').remove();
  436. $('#map-search-results').append(error_response);
  437. mapquery();
  438. },
  439. success: function(data){
  440. $('#map-search-results .loader').remove();
  441. $('#map-search-results').append(data);
  442. /* FORMATTING THE TITLES AT THE TOP OF THE SIDEBAR */
  443. // If a destination is loaded, we adjust the CSS for the Destination Guide title
  444. if (leftJustifyTitle)
  445. $('#dest-guide-title').css('margin-left', '15px');
  446. // If a false destination is loaded, 'Your Location' is removed in a script in main.php.
  447. // Now we check to see if that happened, and if it did, we remove the duplicate title.
  448. if($('#location-heading').html() == 'Destination Guide Picks')
  449. $('#dest-guide-title').remove();
  450. /* END FORMATTING */
  451. // Make this results area of the sidebar scroll by limiting its
  452. // height. We don't know until it's added to the page how far down
  453. // the sidebar it starts (that is, how much stuff is above it in
  454. // the sidebar). So it has to be layed out as a relative element
  455. // without placement at first for us to find its top.
  456. var showcaseContainer = $('.showcase-container');
  457. if (showcaseContainer.length) {
  458. var showcaseContainerTop = showcaseContainer.position().top || '0px';
  459. showcaseContainer.css({
  460. 'position': 'absolute',
  461. 'top': showcaseContainerTop,
  462. 'bottom': '10px'
  463. });
  464. }
  465. mapquery();
  466. }
  467. });
  468. }
  469. /**
  470. * Makes a marker & map window tied to a particular sidebar search result or
  471. * Destination Guide item. When the marker is clicked & the map window opens,
  472. * the sidebar item becomes selected; it becomes deselected again when the map
  473. * window is closed. Clicking the sidebar item pans the map to the marker &
  474. * opens the map window.
  475. *
  476. * @param {Element|String} windowContent the content of the marker's map window
  477. * @param {Point} markerLocation the map location to place the marker at
  478. * @param {JQueryResult} $sidebarItem the jQuery set representing the marker's related sidebar item
  479. * @param {String} regionLocation the SL region location string to link the window's Join button to when the window is opened (optional)
  480. */
  481. function makeMarkerForSidebar(windowContent, markerLatLng, $sidebarItem, regionLocation)
  482. {
  483. var domId = $sidebarItem.attr('id');
  484. var marker = L.marker(markerLatLng, {
  485. icon: L.icon({
  486. iconUrl: assetsURL + "marker-sm.png",
  487. iconSize: [53, 48],
  488. iconAnchor: [26, 48]
  489. })
  490. }).addTo($map);
  491. // TODO: 350 is the declared width of the .balloon-content div wrapping the
  492. // windowContent. Someday we should figure out how to let Leaflet figure
  493. // that out instead of hardcoding it here?
  494. marker.bindPopup(windowContent, {minWidth: 350}).on('popupopen', function (event) {
  495. $('#' + domId).addClass('result-selected');
  496. if (!regionLocation) {
  497. return;
  498. }
  499. var $joinButton = $(event.popup.getPane()).find('a.join_button');
  500. if (0 < $joinButton.length) {
  501. directSlurl($joinButton, regionLocation);
  502. }
  503. }).on('popupclose', function (event) {
  504. $('#' + domId).removeClass('result-selected');
  505. });
  506. $sidebarItem.click(function (evt) {
  507. evt.preventDefault();
  508. marker.togglePopup(); // ???
  509. });
  510. return marker;
  511. }
  512. /* =========================================================================
  513. ** loadmap
  514. ** =========================================================================
  515. ** A singular region or default region, no searches have been made */
  516. function loadmap(firstJoinUrl)
  517. {
  518. var zoomLevel = 6;
  519. // Select our old Ahern default, even if we don't put a marker there. This can
  520. // happen if the directly linked region name is no longer valid (the error
  521. // case), or until the sidebar loads.
  522. // var latlng = L.latLng(1002.5, 997.5);
  523. var latlng = L.latLng(3650.5, 3650.5);
  524. var $body = $('body');
  525. if ($body.data('region-coords-error')) {
  526. var errorContent = slurl_data['noexist_windowcontent'];
  527. // let's not link to nothing, now
  528. $('#marker0').remove();
  529. $('#location-heading').html('Destination Guide Picks');
  530. $('#dest-guide-title').remove();
  531. $('body').prepend(errorContent);
  532. $('#map-error #error-content .error-close').click(function () {
  533. $('#map-error').hide();
  534. });
  535. $map.setView(latlng, zoomLevel);
  536. }
  537. else if (!slurl_data['region']['default']) {
  538. var x = parseInt($body.data('region-coords-x')) + slurl_data['region']['x'] / 256;
  539. var y = parseInt($body.data('region-coords-y')) + slurl_data['region']['y'] / 256;
  540. if (is_number(x) && is_number(y)) latlng = L.latLng(y, x);
  541. var bubbleContent = slurl_data['windowcontent'].replace('/\s+/', ' ');
  542. var $content = $('<div/>').html(bubbleContent);
  543. if (firstJoinUrl) {
  544. $content.find('a.join_button').attr('href', firstJoinUrl);
  545. }
  546. var marker = makeMarkerForSidebar($content.get(0), latlng, $('#marker0'));
  547. $map.setView(latlng, MAX_ZOOM_LEVEL);
  548. marker.openPopup();
  549. }
  550. else {
  551. $map.setView(latlng, zoomLevel);
  552. }
  553. }
  554. /**
  555. * Try to decode the given string from a URI component to plain unencoded text.
  556. * In addition to the normal `decodeURIComponent()` decoding, plus `+`
  557. * characters, which are a historic URL encoding of spaces (such as by PHP
  558. * `urlencode`, vs `rawurlencode`), are decoded to spaces. If the given text
  559. * cannot be properly decoded (an unencoded percent sign `%` is present, the
  560. * encoded characters represent an invalid byte sequence in the current
  561. * character encoding, etc), the empty string is returned rather than a
  562. * JavaScript error being raised.
  563. *
  564. * @param {String} text the URI component to decode
  565. * @return {String} the decoded text represented by the URI component (or the empty string)
  566. */
  567. function safeDecodeURIComponent(text)
  568. {
  569. try {
  570. return decodeURIComponent(text.replace(/\+/g, ' '));
  571. }
  572. catch (err) {}
  573. return '';
  574. }
  575. /* =========================================================================
  576. ** mapquery
  577. ** =========================================================================
  578. ** A query has been made, plot those points */
  579. function mapquery()
  580. {
  581. $("a.slurl-link").each(function (index) {
  582. var xy = $(this).attr("title").split(",");
  583. var rx = parseFloat(xy[0]);
  584. var ry = parseFloat(xy[1]);
  585. var linkHref = $(this).attr("href");
  586. var title = '';
  587. var msg = '';
  588. var img = '';
  589. /*//////////////////////////////////////////////////////////////////////
  590. // Must string replace the base url for IE because ie passes fully qualified url to javascript
  591. // even though it doesn't exist in the markup
  592. //////////////////////////////////////////////////////////////////////*/
  593. var re_tp_url = new RegExp('^(https?://[^/]+)?/secondlife/([^?]+).*$', 'i');
  594. // linkHref is still URI encoded, so tp_url ends up with the correct URI encoded name.
  595. var tp_region = linkHref.replace(re_tp_url, "$2");
  596. var tp_url = "secondlife://" + tp_region;
  597. if (rx && ry && tp_region) {
  598. var tokens = tp_region.split('/');
  599. loc_x = rx + parseFloat(tokens[1]) / 256;
  600. loc_y = ry + parseFloat(tokens[2]) / 256;
  601. }
  602. else {
  603. // Error with precise location plotting in the region - set to center
  604. loc_x = 128;
  605. loc_y = 128;
  606. msg = 'We\'re having difficulty plotting this location. It could be that this isn\'t a valid location, or that you are trying to locate a person.';
  607. tp_url = null;
  608. }
  609. var templateData = {
  610. 'slurl': tp_url,
  611. 'title': title || $.sl.maps.config.default_title,
  612. 'img': img || $.sl.maps.config.default_img,
  613. 'msg': msg || $.sl.maps.config.default_msg
  614. };
  615. Mustache.parse($.sl.maps.config.balloon_tmpl);
  616. var windowContent = Mustache.render($.sl.maps.config.balloon_tmpl, templateData);
  617. var markerLatLng = L.latLng(loc_y, loc_x);
  618. var $result = $(this).parents('div.result').first();
  619. var regionLocation = '/secondlife/' + tp_region;
  620. makeMarkerForSidebar(windowContent, markerLatLng, $result, regionLocation);
  621. });
  622. // Select the first result if we didn't load with a place selected (that
  623. // is, a search or a default maps.sl.com page load).
  624. if (slurl_data['region']['default']) {
  625. $("a.slurl-link").first().click();
  626. }
  627. }
  628. ;
  629. var urlParams = {};
  630. var slurl_data = new Object();
  631. (function () {
  632. var e,
  633. a = /\+/g, // Regex for replacing addition symbol with a space
  634. r = /([^&=]+)=?([^&]*)/g,
  635. d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
  636. q = window.location.search.substring(1);
  637. while (e = r.exec(q))
  638. urlParams[d(e[1])] = safeDecodeURIComponent(e[2]);
  639. })();
  640. // Aggregates all data pertaining to a slurl - Includes title, image, description, x/y coordinates, etc
  641. function slurl_setup() {
  642. var urlPath = window.location.pathname;
  643. var urlParts = urlPath.split("/");
  644. // var initial_region = 'Ahern';
  645. var initial_region = 'Beta Technologies';
  646. slurl_data['region'] = new Object();
  647. if (urlParts[1] == '' || urlParts[1] == 'index.php' || urlParams['q'] != undefined || urlParts[3] == undefined) {
  648. slurl_data['region']['name'] = initial_region;
  649. slurl_data['region']['x'] = 0;
  650. slurl_data['region']['y'] = 0;
  651. slurl_data['region']['z'] = 0;
  652. slurl_data['region']['default'] = true;
  653. }
  654. else
  655. {
  656. initial_region = (urlParts[2] == undefined) ? 'Beta Technologies' : decodeURIComponent(urlParts[2]);
  657. slurl_data['region']['name'] = initial_region;
  658. // The following 2 should be numeric only -- need to regex 0-256
  659. slurl_data['region']['x'] = (urlParts[3] != undefined) ? check_coords(urlParts[3]) : 128 ;
  660. slurl_data['region']['y'] = (urlParts[4] != undefined) ? check_coords(urlParts[4]) : 128 ;
  661. // z-index can much higher than 256, 4 digits at least, here we have an uncapped check
  662. slurl_data['region']['z'] = (urlParts[5] != undefined) ? urlParts[5] : 0;
  663. slurl_data['region']['default'] = false;
  664. }
  665. // hurl the slurl
  666. var slurl = 'secondlife://' + encodeURIComponent(slurl_data['region']['name'].toUpperCase()) + '/' + slurl_data['region']['x'] + '/' + slurl_data['region']['y'] + '/' + slurl_data['region']['z'];
  667. // Setup the template data
  668. var templateData = {
  669. 'slurl': slurl,
  670. 'title': $.sl.maps.config.default_title,
  671. 'img': $.sl.maps.config.default_img,
  672. 'msg': $.sl.maps.config.default_msg
  673. };
  674. // Create the balloon content
  675. Mustache.parse($.sl.maps.config.balloon_tmpl);
  676. slurl_data['windowcontent'] = Mustache.render($.sl.maps.config.balloon_tmpl, templateData);
  677. // Some content for a sidebar if needed
  678. slurl_data['sidebarcontent'] = templateData;
  679. // A default for when a region doesn't exist.
  680. Mustache.parse($.sl.maps.config.noexists_tmpl);
  681. slurl_data['noexist_windowcontent'] = Mustache.render($.sl.maps.config.noexists_tmpl, {'region_name':slurl_data['region']['name']} );
  682. }
  683. // Check that a coordinate within a region is valid. If not, return the center of the region
  684. function check_coords(slurl_coord)
  685. {
  686. if (is_number(slurl_coord) && ((0 <= slurl_coord) && (slurl_coord <= 256))) {
  687. return slurl_coord;
  688. }
  689. else {
  690. return 128;
  691. }
  692. }
  693. function is_number(n) {
  694. return !isNaN(parseFloat(n)) && isFinite(n);
  695. }
  696. slurl_setup();
  697. // application.js
  698. ;