diff --git a/README.rst b/README.rst
index ae949d4..a0c995e 100644
--- a/README.rst
+++ b/README.rst
@@ -18,7 +18,7 @@ Blog:
The Minecraft Overviewer is a command-line tool for rendering high-resolution
maps of Minecraft worlds. It generates a set of static html and image files and
-uses the Google Maps API to display a nice interactive map.
+uses Leaflet to display a nice interactive map.
The Overviewer has been in active development for several years and has many
features, including day and night lighting, cave rendering, mineral overlays,
@@ -66,16 +66,9 @@ Viewing the Results
-------------------
Within the output directory you will find two things: an index.html file, and a
directory hierarchy full of images. To view your world, simply open index.html
-in a web browser. Internet access is required to load the Google Maps API
-files, but you otherwise don't need anything else.
+in a web browser.
-You can throw these files up to a web server to let others view your map. To
-ensure the map works when viewed through the Internet, you'll need a Google
-Maps API key, which you can `get for free from Google`_. Please note that you
-are also bound by the `Google Maps API Terms of Service`_.
-
-.. _get for free from Google: https://developers.google.com/maps/documentation/javascript/get-api-key
-.. _Google Maps API Terms of Service: https://developers.google.com/maps/terms
+You can throw these files up to a web server to let others view your map.
Bugs
====
diff --git a/docs/design/designdoc.rst b/docs/design/designdoc.rst
index 8b9a943..2196e63 100644
--- a/docs/design/designdoc.rst
+++ b/docs/design/designdoc.rst
@@ -25,8 +25,8 @@ Background Info
===============
The Overviewer's task is to take Minecraft worlds and render them into a set of
-tiles that can be displayed with a Google Maps interface. This section goes over
-how Minecraft worlds work and are stored.
+tiles that can be displayed with a Leaflet interface. This section goes over how
+Minecraft worlds work and are stored.
A Minecraft world extends indefinitely along the two horizontal axes, and are
exactly 256 units high. Minecraft worlds are made of voxels (volumetric pixels),
@@ -455,12 +455,12 @@ quickly become too much data to handle at once. (Early versions of the
Overviewer did this, but the large, unwieldy images quickly motivated the
development of rendering to individual tiles).
-Hence choosing a technology like Google Maps, which draws small tiles together
-to make it look like one large image, lets rendering even the largest worlds
-possible. The Overviewer can draw each tile separately and not have to load the
-entire map into memory at once. The next sections describe how to determine
-which chunks to render in which tiles, and how to reason about tile ↔ chunk
-mappings.
+Hence choosing a technology like Google Maps or Leaflet, which draws small
+tiles together to make it look like one large image, lets rendering even the
+largest worlds possible. The Overviewer can draw each tile separately and not
+have to load the entire map into memory at once. The next sections describe
+how to determine which chunks to render in which tiles, and how to reason
+about tile ↔ chunk mappings.
Tile Layout
-----------
diff --git a/docs/faq.rst b/docs/faq.rst
index 2fbafc7..1b9dda4 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -19,10 +19,10 @@ supporting mod blocks is not trivial.
Can I view Overviewer maps without having an internet connection?
-----------------------------------------------------------------
-Not at the moment. The Overviewer relies on the Google maps API to display
-maps, which your browser needs to load from Google. However, switching away
-from Google Maps is something that will most likely be looked into in the
-future.
+Yes, absolutely. The Overviewer switched away from the Google Maps API and
+now uses Leaflet. All files which Overviewer needs are included in the output,
+so even if you have no internet connection, you will still be able to view the
+map without any issues.
When my map expands, I see remnants of another zoom level
---------------------------------------------------------
diff --git a/docs/index.rst b/docs/index.rst
index 1769ae2..c16017c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -19,7 +19,7 @@ Introduction
============
The Minecraft Overviewer is a command-line tool for rendering high-resolution
maps of Minecraft worlds. It generates a set of static html and image files and
-uses the Google Maps API to display a nice interactive map.
+uses Leaflet to display a nice interactive map.
The Overviewer has been in active development for several years and has many
features, including day and night lighting, cave rendering, mineral overlays,
@@ -64,7 +64,7 @@ Features
* Choose from four rendering angles.
-* Generates a Google Maps powered map!
+* Generates a Leaflet powered map!
* Runs on Linux, Windows, and Mac platforms!
diff --git a/overviewer_core/assetmanager.py b/overviewer_core/assetmanager.py
index f58712c..a0a4b82 100644
--- a/overviewer_core/assetmanager.py
+++ b/overviewer_core/assetmanager.py
@@ -27,6 +27,7 @@ import world
import util
from files import FileReplacer, mirror_dir, get_fs_caps
+
class AssetManager(object):
"""\
These objects provide an interface to metadata and persistent data, and at the
@@ -36,9 +37,9 @@ There should only be one instances of these per execution.
def __init__(self, outputdir, custom_assets_dir=None):
"""\
-Initializes the AssetManager with the top-level output directory.
-It can read/parse and write/dump the overviewerConfig.js file into this top-level
-directory.
+Initializes the AssetManager with the top-level output directory.
+It can read/parse and write/dump the overviewerConfig.js file into this
+top-level directory.
"""
self.outputdir = outputdir
self.custom_assets_dir = custom_assets_dir
@@ -47,13 +48,16 @@ directory.
self.fs_caps = get_fs_caps(self.outputdir)
# look for overviewerConfig in self.outputdir
+ config_loc = os.path.join(self.outputdir, "overviewerConfig.js")
try:
- with open(os.path.join(self.outputdir, "overviewerConfig.js")) as c:
- overviewerConfig_str = "{" + "\n".join(c.readlines()[1:-1]) + "}"
- self.overviewerConfig = json.loads(overviewerConfig_str)
+ with open(config_loc) as c:
+ ovconf_str = "{" + "\n".join(c.readlines()[1:-1]) + "}"
+ self.overviewerConfig = json.loads(ovconf_str)
except Exception, e:
- if os.path.exists(os.path.join(self.outputdir, "overviewerConfig.js")):
- logging.warning("A previous overviewerConfig.js was found, but I couldn't read it for some reason. Continuing with a blank config")
+ if os.path.exists(config_loc):
+ logging.warning("A previous overviewerConfig.js was found, "
+ "but I couldn't read it for some reason."
+ "Continuing with a blank config")
logging.debug(traceback.format_exc())
self.overviewerConfig = dict(tilesets=dict())
@@ -73,7 +77,6 @@ directory.
if conf['path'] == name:
return conf
return dict()
-
def initialize(self, tilesets):
"""Similar to finalize() but calls the tilesets' get_initial_data()
@@ -92,26 +95,36 @@ directory.
def _output_assets(self, tilesets, initial):
if not initial:
- get_data = lambda tileset: tileset.get_persistent_data()
+ def get_data(tileset):
+ return tileset.get_persistent_data()
else:
- get_data = lambda tileset: tileset.get_initial_data()
+ def get_data(tileset):
+ return tileset.get_initial_data()
- # dictionary to hold the overviewerConfig.js settings that we will dumps
+ # dictionary to hold the overviewerConfig.js settings that we will dump
+ # to JSON using dumps
dump = dict()
dump['CONST'] = dict(tileSize=384)
dump['CONST']['image'] = {
- 'defaultMarker': 'signpost.png',
- 'signMarker': 'signpost_icon.png',
- 'bedMarker': 'bed.png',
- 'spawnMarker': 'https://google-maps-icons.googlecode.com/files/home.png',
- 'queryMarker': 'https://google-maps-icons.googlecode.com/files/regroup.png'
- }
+ 'defaultMarker': 'signpost.png',
+ 'signMarker': 'signpost_icon.png',
+ 'bedMarker': 'bed.png',
+ 'spawnMarker': 'icons/marker_home.png',
+ 'spawnMarker2x': 'icons/marker_home_2x.png',
+ 'queryMarker': 'icons/marker_location.png',
+ 'queryMarker2x': 'icons/marker_location_2x.png'
+ }
dump['CONST']['mapDivId'] = 'mcmap'
- dump['CONST']['regionStrokeWeight'] = 2 # Obselete
- dump['CONST']['UPPERLEFT'] = world.UPPER_LEFT;
- dump['CONST']['UPPERRIGHT'] = world.UPPER_RIGHT;
- dump['CONST']['LOWERLEFT'] = world.LOWER_LEFT;
- dump['CONST']['LOWERRIGHT'] = world.LOWER_RIGHT;
+ dump['CONST']['UPPERLEFT'] = world.UPPER_LEFT
+ dump['CONST']['UPPERRIGHT'] = world.UPPER_RIGHT
+ dump['CONST']['LOWERLEFT'] = world.LOWER_LEFT
+ dump['CONST']['LOWERRIGHT'] = world.LOWER_RIGHT
+ dump['CONST']['image']['compass'] = {
+ world.UPPER_LEFT: 'compass_upper-left.png',
+ world.UPPER_RIGHT: 'compass_upper-right.png',
+ world.LOWER_LEFT: 'compass_lower-left.png',
+ world.LOWER_RIGHT: 'compass_lower-right.png'
+ }
# based on the tilesets we have, group them by worlds
worlds = []
@@ -124,7 +137,7 @@ directory.
dump['map'] = dict()
dump['map']['debug'] = True
dump['map']['cacheTag'] = str(int(time.time()))
- dump['map']['north_direction'] = 'lower-left' # only temporary
+ dump['map']['north_direction'] = 'lower-left' # only temporary
dump['map']['center'] = [-314, 67, 94]
dump['map']['controls'] = {
'pan': True,
@@ -134,13 +147,10 @@ directory.
'mapType': True,
'overlays': True,
'coordsBox': True,
- 'searchBox': True # Lolwat. Obselete
}
-
dump['tilesets'] = []
-
for tileset in tilesets:
dump['tilesets'].append(get_data(tileset))
@@ -152,38 +162,42 @@ directory.
# write out config
jsondump = json.dumps(dump, indent=4)
- with FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js"), capabilities=self.fs_caps) as tmpfile:
+ with FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js"),
+ capabilities=self.fs_caps) as tmpfile:
with codecs.open(tmpfile, 'w', encoding='UTF-8') as f:
f.write("var overviewerConfig = " + jsondump + ";\n")
- #Copy assets, modify index.html
- self.output_noconfig()
-
+ # Copy assets, modify index.html
+ self.output_noconfig()
def output_noconfig(self):
-
# copy web assets into destdir:
- global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets")
+ global_assets = os.path.join(util.get_program_path(),
+ "overviewer_core", "data", "web_assets")
if not os.path.isdir(global_assets):
global_assets = os.path.join(util.get_program_path(), "web_assets")
mirror_dir(global_assets, self.outputdir, capabilities=self.fs_caps)
if self.custom_assets_dir:
- # Could have done something fancy here rather than just overwriting
- # the global files, but apparently this what we used to do pre-rewrite.
- mirror_dir(self.custom_assets_dir, self.outputdir, capabilities=self.fs_caps)
-
- # write a dummy baseMarkers.js if none exists
- if not os.path.exists(os.path.join(self.outputdir, "baseMarkers.js")):
- with open(os.path.join(self.outputdir, "baseMarkers.js"), "w") as f:
- f.write("// if you wants signs, please see genPOI.py\n");
+ # We could have done something fancy here rather than just
+ # overwriting the global files, but apparently this what we used to
+ # do pre-rewrite.
+ mirror_dir(self.custom_assets_dir, self.outputdir,
+ capabilities=self.fs_caps)
+ # write a dummy baseMarkers.js if none exists
+ basemarkers_path = os.path.join(self.outputdir, "baseMarkers.js")
+ if not os.path.exists(basemarkers_path):
+ with open(basemarkers_path, "w") as f:
+ f.write("// if you wants signs, please see genPOI.py\n")
# create overviewer.js from the source js files
- js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src")
+ js_src = os.path.join(util.get_program_path(),
+ "overviewer_core", "data", "js_src")
if not os.path.isdir(js_src):
js_src = os.path.join(util.get_program_path(), "js_src")
- with FileReplacer(os.path.join(self.outputdir, "overviewer.js"), capabilities=self.fs_caps) as tmpfile:
+ with FileReplacer(os.path.join(self.outputdir, "overviewer.js"),
+ capabilities=self.fs_caps) as tmpfile:
with open(tmpfile, "w") as fout:
# first copy in js_src/overviewer.js
with open(os.path.join(js_src, "overviewer.js"), 'r') as f:
@@ -191,16 +205,20 @@ directory.
# now copy in the rest
for js in os.listdir(js_src):
if not js.endswith("overviewer.js") and js.endswith(".js"):
- with open(os.path.join(js_src,js)) as f:
+ with open(os.path.join(js_src, js)) as f:
fout.write(f.read())
-
+
# Add time and version in index.html
indexpath = os.path.join(self.outputdir, "index.html")
index = codecs.open(indexpath, 'r', encoding='UTF-8').read()
index = index.replace("{title}", "Minecraft Overviewer")
- index = index.replace("{time}", time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime()).decode(self.preferredencoding))
- versionstr = "%s (%s)" % (util.findGitVersion(), util.findGitHash()[:7])
+ index = index.replace("{time}",
+ time.strftime("%a, %d %b %Y %H:%M:%S %Z",
+ time.localtime())
+ .decode(self.preferredencoding))
+ versionstr = "%s (%s)" % (util.findGitVersion(),
+ util.findGitHash()[:7])
index = index.replace("{version}", versionstr)
with FileReplacer(indexpath, capabilities=self.fs_caps) as indexpath:
diff --git a/overviewer_core/data/js_src/models.js b/overviewer_core/data/js_src/models.js
deleted file mode 100644
index 930d36d..0000000
--- a/overviewer_core/data/js_src/models.js
+++ /dev/null
@@ -1,47 +0,0 @@
-overviewer.models = {};
-
-/* WorldModel
- * Primarily has a collection of TileSets
- */
-overviewer.models.WorldModel = Backbone.Model.extend({
- initialize: function(attrs) {
- attrs.tileSets = new overviewer.models.TileSetCollection();
- this.set(attrs);
- }
-});
-
-
-/* WorldCollection
- * A collection of WorldModels
- */
-overviewer.models.WorldCollection = Backbone.Collection.extend({
- model: overviewer.models.WorldModel
-});
-
-
-/* TileSetModel
- */
-overviewer.models.TileSetModel = Backbone.Model.extend({
- defaults: {
- markers: []
- },
- initialize: function(attrs) {
- // this implies that the Worlds collection must be
- // initialized before any TIleSetModels are created
- attrs.world = overviewer.collections.worlds.get(attrs.world);
- this.set(attrs);
- }
-});
-
-overviewer.models.TileSetCollection = Backbone.Collection.extend({
- model: overviewer.models.TileSetModel
-});
-
-
-overviewer.models.GoogleMapModel = Backbone.Model.extend({
- initialize: function(attrs) {
- attrs.currentWorldView = overviewer.collections.worldViews[0];
- this.set(attrs);
- }
-});
-
diff --git a/overviewer_core/data/js_src/overviewer.js b/overviewer_core/data/js_src/overviewer.js
index f8de2d0..8261d7a 100644
--- a/overviewer_core/data/js_src/overviewer.js
+++ b/overviewer_core/data/js_src/overviewer.js
@@ -11,8 +11,15 @@ var overviewer = {};
* This holds the map, probably the most important var in this file
*/
overviewer.map = null;
-overviewer.mapView = null;
+overviewer.worldCtrl = null;
+overviewer.layerCtrl = null;
+overviewer.compass = null;
+overviewer.coord_box = null;
+overviewer.current_world = null;
+/// Records the current layer by name (if any) of each world
+overviewer.current_layer = {};
+
overviewer.collections = {
/**
@@ -29,6 +36,14 @@ overviewer.collections = {
*/
'infoWindow': null,
+ /**
+ * When switching regionsets, where should we zoom to?
+ * Defaults to spawn. Stored as map of world names to [latlng, zoom]
+ */
+ 'centers': {},
+
+ 'overlays': {},
+
'worldViews': [],
'haveSigns': false,
@@ -39,13 +54,14 @@ overviewer.collections = {
'markerInfo': {},
/**
- * holds a reference to the spawn marker.
+ * holds a reference to the spawn marker.
*/
'spawnMarker': null,
-
- /**
- * if a user visits a specific URL, this marker will point to the coordinates in the hash
- */
+
+ /**
+ * if a user visits a specific URL, this marker will point to the
+ * coordinates in the hash
+ */
'locationMarker': null
};
@@ -72,39 +88,3 @@ overviewer.classes = {
}
};
-
-
-overviewer.gmap = {
-
- /**
- * Generate a function to get the path to a tile at a particular location
- * and zoom level.
- *
- * @param string path
- * @param string pathBase
- * @param string pathExt
- */
- 'getTileUrlGenerator': function(path, pathBase, pathExt) {
- return function(tile, zoom) {
- var url = path;
- var urlBase = ( pathBase ? pathBase : '' );
- if(tile.x < 0 || tile.x >= Math.pow(2, zoom) ||
- tile.y < 0 || tile.y >= Math.pow(2, zoom)) {
- url += '/blank';
- } else if(zoom === 0) {
- url += '/base';
- } else {
- for(var z = zoom - 1; z >= 0; --z) {
- var x = Math.floor(tile.x / Math.pow(2, z)) % 2;
- var y = Math.floor(tile.y / Math.pow(2, z)) % 2;
- url += '/' + (x + 2 * y);
- }
- }
- url = url + '.' + pathExt;
- if(typeof overviewerConfig.map.cacheTag !== 'undefined') {
- url += '?c=' + overviewerConfig.map.cacheTag;
- }
- return(urlBase + url);
- };
- }
-};
diff --git a/overviewer_core/data/js_src/util.js b/overviewer_core/data/js_src/util.js
index 6121cf5..97a9595 100644
--- a/overviewer_core/data/js_src/util.js
+++ b/overviewer_core/data/js_src/util.js
@@ -32,145 +32,296 @@ overviewer.util = {
* feature gets added.
*/
'initialize': function() {
- overviewer.util.initializeClassPrototypes();
+ //overviewer.util.initializeClassPrototypes();
+ overviewer.util.initializePolyfills();
+ overviewer.util.initializeMarkers();
- overviewer.collections.worlds = new overviewer.models.WorldCollection();
+ document.getElementById('NoJSWarning').remove();
- $.each(overviewerConfig.worlds, function(index, el) {
- var n = new overviewer.models.WorldModel({name: el, id:el});
- overviewer.collections.worlds.add(n);
- });
+ overviewer.coordBoxClass = L.Control.extend({
+ options: {
+ position: 'bottomleft',
+ },
+ initialize: function() {
+ this.coord_box = L.DomUtil.create('div', 'coordbox');
+ },
+ render: function(latlng) {
+ var currWorld = overviewer.current_world;
+ if (currWorld == null) {return;}
- $.each(overviewerConfig.tilesets, function(index, el) {
- var newTset = new overviewer.models.TileSetModel(el);
- overviewer.collections.worlds.get(el.world).get("tileSets").add(newTset);
- });
+ var currTileset = overviewer.current_layer[currWorld];
+ if (currTileset == null) {return;}
- overviewer.collections.worlds.each(function(world, index, list) {
- var nv = new overviewer.views.WorldView({model: world});
- overviewer.collections.worldViews.push(nv);
- });
+ var ovconf = currTileset.tileSetConfig;
- overviewer.mapModel = new overviewer.models.GoogleMapModel({});
- overviewer.mapView = new overviewer.views.GoogleMapView({el: document.getElementById(overviewerConfig.CONST.mapDivId), model:overviewer.mapModel});
+ w_coords = overviewer.util.fromLatLngToWorld(latlng.lat, latlng.lng, ovconf);
- // any controls must be created after the GoogleMapView is created
- // controls should be added in the order they should appear on screen,
- // with controls on the outside of the page being added first
+ var r_x = Math.floor(Math.floor(w_coords.x / 16.0) / 32.0);
+ var r_z = Math.floor(Math.floor(w_coords.z / 16.0) / 32.0);
+ var r_name = "r." + r_x + "." + r_z + ".mca";
- var compass = new overviewer.views.CompassView({tagName: 'DIV', model:overviewer.mapModel});
- // no need to render the compass now. it's render event will get fired by
- // the maptypeid_chagned event
-
- var coordsdiv = new overviewer.views.CoordboxView({tagName: 'DIV'});
- coordsdiv.render();
-
- var progressdiv = new overviewer.views.ProgressView({tagName: 'DIV'});
- progressdiv.render();
- progressdiv.updateProgress();
-
- if (overviewer.collections.haveSigns) {
- var signs = new overviewer.views.SignControlView();
- signs.registerEvents(signs);
- }
-
- var overlayControl = new overviewer.views.OverlayControlView();
-
- var spawnmarker = new overviewer.views.SpawnIconView();
-
- // Update coords on mousemove
- google.maps.event.addListener(overviewer.map, 'mousemove', function (event) {
- coordsdiv.updateCoords(event.latLng);
+ this.coord_box.innerHTML = "X " +
+ Math.round(w_coords.x) +
+ " Z " + Math.round(w_coords.z) +
+ " (" + r_name + ")";
+ },
+ onAdd: function() {
+ return this.coord_box;
+ }
});
- google.maps.event.addListener(overviewer.map, 'idle', function (event) {
+ overviewer.compassClass = L.Control.extend({
+ initialize: function(imagedict, options) {
+ L.Util.setOptions(this, options);
+ this.compass_img = L.DomUtil.create('img', 'compass');
+ this.imagedict = imagedict;
+ },
+ render: function(direction) {
+ this.compass_img.src = this.imagedict[direction];
+ },
+ onAdd: function() {
+ return this.compass_img;
+ }
+ });
+ overviewer.control = L.Control.extend({
+ initialize: function(options) {
+ L.Util.setOptions(this, options);
+
+ this.container = L.DomUtil.create('div', 'worldcontrol');
+ this.select = L.DomUtil.create('select');
+ this.select.onchange = this.onChange;
+ this.container.appendChild(this.select);
+ },
+ addWorld: function(world) {
+ var option = L.DomUtil.create('option');
+ option.value = world;
+ option.innerText = world;
+ this.select.appendChild(option);
+ },
+ onChange: function(ev) {
+ console.log(ev.target);
+ console.log(ev.target.value);
+ var selected_world = ev.target.value;
+
+
+ // save current view for the current_world
+ overviewer.collections.centers[overviewer.current_world][0] = overviewer.map.getCenter();
+ overviewer.collections.centers[overviewer.current_world][1] = overviewer.map.getZoom();
+
+ overviewer.layerCtrl.remove();
+
+ overviewer.layerCtrl = L.control.layers(
+ overviewer.collections.mapTypes[selected_world],
+ overviewer.collections.overlays[selected_world],
+ {collapsed: false})
+ .addTo(overviewer.map);
+
+ for (var world_name in overviewer.collections.mapTypes) {
+ for (var tset_name in overviewer.collections.mapTypes[world_name]) {
+ var lyr = overviewer.collections.mapTypes[world_name][tset_name];
+ if (world_name != selected_world) {
+ if (overviewer.map.hasLayer(lyr))
+ overviewer.map.removeLayer(lyr);
+ }
+ }
+
+ for (var tset_name in overviewer.collections.overlays[world_name]) {
+ var lyr = overviewer.collections.overlays[world_name][tset_name];
+ if (world_name != selected_world) {
+ if (overviewer.map.hasLayer(lyr))
+ overviewer.map.removeLayer(lyr);
+ }
+ }
+ }
+
+ var center = overviewer.collections.centers[selected_world];
+ overviewer.map.setView(center[0], center[1]);
+
+ overviewer.current_world = selected_world;
+
+ if (overviewer.collections.mapTypes[selected_world] && overviewer.current_layer[selected_world]) {
+ overviewer.map.addLayer(overviewer.collections.mapTypes[selected_world][overviewer.current_layer[selected_world].tileSetConfig.name]);
+ } else {
+ var tset_name = Object.keys(overviewer.collections.mapTypes[selected_world])[0]
+ overviewer.map.addLayer(overviewer.collections.mapTypes[selected_world][tset_name]);
+ }
+ },
+ onAdd: function() {
+ console.log("onAdd mycontrol");
+
+ return this.container
+ }
+ });
+
+
+
+ overviewer.map = L.map('mcmap', {
+ crs: L.CRS.Simple,
+ minZoom: 0});
+
+ overviewer.map.attributionControl.setPrefix(
+ 'Overviewer/Leaflet');
+
+ overviewer.map.on('baselayerchange', function(ev) {
+ overviewer.current_layer[overviewer.current_world] = ev.layer;
+ var ovconf = ev.layer.tileSetConfig;
+
+ // Change the compass
+ overviewer.compass.render(ovconf.north_direction);
+
+ // Set the background colour
+ document.getElementById("mcmap").style.backgroundColor = ovconf.bgcolor;
+
+ if (overviewer.collections.locationMarker) {
+ overviewer.collections.locationMarker.remove();
+ }
+ // Remove old spawn marker, add new one
+ if (overviewer.collections.spawnMarker) {
+ overviewer.collections.spawnMarker.remove();
+ }
+ if (typeof(ovconf.spawn) == "object") {
+ var spawnIcon = L.icon({
+ iconUrl: overviewerConfig.CONST.image.spawnMarker,
+ iconRetinaUrl: overviewerConfig.CONST.image.spawnMarker2x,
+ iconSize: [32, 37],
+ iconAnchor: [15, 33],
+ });
+ var latlng = overviewer.util.fromWorldToLatLng(ovconf.spawn[0],
+ ovconf.spawn[1],
+ ovconf.spawn[2],
+ ovconf);
+ var ohaimark = L.marker(latlng, {icon: spawnIcon, title: "Spawn"});
+ ohaimark.on('click', function(ev) {
+ overviewer.map.setView(ev.latlng);
+ });
+ overviewer.collections.spawnMarker = ohaimark
+ overviewer.collections.spawnMarker.addTo(overviewer.map);
+ } else {
+ overviewer.collections.spawnMarker = null;
+ }
+
+ // reset the markers control with the markers for this layer
+ if (ovconf.marker_groups) {
+ console.log("markers for", ovconf.marker_groups);
+ markerCtrl = L.control.layers(
+ [],
+ ovconf.marker_groups, {collapsed: false}).addTo(overviewer.map);
+ }
+
overviewer.util.updateHash();
});
- google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function(event) {
- // it's handy to keep track of the currently visible tileset. we let
- // the GoogleMapView manage this
- overviewer.mapView.updateCurrentTileset();
-
- compass.render();
- spawnmarker.render();
- if (overviewer.collections.locationMarker) {
- overviewer.collections.locationMarker.setMap(null);
- overviewer.collections.locationMarker = null;
- }
-
- // update list of spawn overlays
- overlayControl.render();
-
- // re-center on the last viewport
- var currentWorldView = overviewer.mapModel.get("currentWorldView");
- if (currentWorldView.options.lastViewport) {
- var x = currentWorldView.options.lastViewport[0];
- var y = currentWorldView.options.lastViewport[1];
- var z = currentWorldView.options.lastViewport[2];
- var zoom = currentWorldView.options.lastViewport[3];
-
- var latlngcoords = overviewer.util.fromWorldToLatLng(x, y, z,
- overviewer.mapView.options.currentTileSet);
- overviewer.map.setCenter(latlngcoords);
-
- if (zoom == 'max') {
- zoom = overviewer.mapView.options.currentTileSet.get('maxZoom');
- } else if (zoom == 'min') {
- zoom = overviewer.mapView.options.currentTileSet.get('minZoom');
- } else {
- zoom = parseInt(zoom);
- if (zoom < 0) {
- // if zoom is negative, treat it as a "zoom out from max"
- zoom += overviewer.mapView.options.currentTileSet.get('maxZoom');
- } else {
- // fall back to default zoom
- zoom = overviewer.mapView.options.currentTileSet.get('defaultZoom');
- }
- }
-
- // clip zoom
- if (zoom > overviewer.mapView.options.currentTileSet.get('maxZoom'))
- zoom = overviewer.mapView.options.currentTileSet.get('maxZoom');
- if (zoom < overviewer.mapView.options.currentTileSet.get('minZoom'))
- zoom = overviewer.mapView.options.currentTileSet.get('minZoom');
-
- overviewer.map.setZoom(zoom);
- }
-
+ overviewer.map.on('moveend', function(ev) {
+ overviewer.util.updateHash();
+ });
+
+ var tset = overviewerConfig.tilesets[0];
+ overviewer.map.on("click", function(e) {
+ console.log(e.latlng);
+ var point = overviewer.util.fromLatLngToWorld(e.latlng.lat, e.latlng.lng, tset);
+ console.log(point);
});
+ var tilesetLayers = {}
- // hook up some events
-
- overviewer.mapModel.bind("change:currentWorldView", overviewer.mapView.render, overviewer.mapView);
-
- overviewer.mapView.render();
-
- // Jump to the hash if given (and do so for any further hash changes)
- overviewer.util.initHash();
- $(window).on('hashchange', function() { overviewer.util.initHash(); });
-
- // create this control after initHash so it can correctly select the current world
- var worldSelector = new overviewer.views.WorldSelectorView({tagName:'DIV'});
- overviewer.collections.worlds.bind("add", worldSelector.render, worldSelector);
-
+ overviewer.worldCtrl = new overviewer.control();
+ overviewer.compass = new overviewer.compassClass(
+ overviewerConfig.CONST.image.compass);
+ overviewer.coord_box = new overviewer.coordBoxClass();
- overviewer.util.initializeMarkers();
- /*
- overviewer.util.initializeMapTypes();
- overviewer.util.initializeMap();
- overviewer.util.initializeRegions();
- overviewer.util.createMapControls();
- */
-
- // run ready callbacks now
- google.maps.event.addListenerOnce(overviewer.map, 'idle', function(){
- // ok now..
- overviewer.util.runReadyQueue();
- overviewer.util.isReady = true;
+ $.each(overviewerConfig.worlds, function(idx, world_name) {
+ overviewer.collections.mapTypes[world_name] = {}
+ overviewer.collections.overlays[world_name] = {}
+ overviewer.worldCtrl.addWorld(world_name);
});
+
+ overviewer.compass.addTo(overviewer.map);
+ overviewer.worldCtrl.addTo(overviewer.map);
+ overviewer.coord_box.addTo(overviewer.map);
+
+ overviewer.map.on('mousemove', function(ev) {
+ overviewer.coord_box.render(ev.latlng);
+ });
+
+ $.each(overviewerConfig.tilesets, function(idx, obj) {
+ var myLayer = new L.tileLayer('', {
+ tileSize: overviewerConfig.CONST.tileSize,
+ noWrap: true,
+ maxZoom: obj.maxZoom,
+ minZoom: obj.minZoom,
+ errorTileUrl: obj.base + obj.path + "/blank." + obj.imgextension,
+ });
+ myLayer.getTileUrl = overviewer.util.getTileUrlGenerator(obj.path, obj.base, obj.imgextension);
+
+ if (obj.isOverlay) {
+ overviewer.collections.overlays[obj.world][obj.name] = myLayer;
+ } else {
+ overviewer.collections.mapTypes[obj.world][obj.name] = myLayer;
+ }
+
+ obj.marker_groups = {};
+
+ if (overviewer.collections.haveSigns == true) {
+ // if there are markers for this tileset, create them now
+ if ((typeof markers !== 'undefined') && (obj.path in markers)) {
+ console.log("this tileset has markers:", obj);
+
+ for (var mkidx = 0; mkidx < markers[obj.path].length; mkidx++) {
+ var marker_group = new L.layerGroup();
+ var marker_entry = markers[obj.path][mkidx];
+ var icon = L.icon({iconUrl: marker_entry.icon,
+ iconSize: [32, 32]});
+ console.log("marker group:", marker_entry.displayName, marker_entry.groupName);
+
+ for (var dbidx = 0; dbidx < markersDB[marker_entry.groupName].raw.length; dbidx++) {
+ var db = markersDB[marker_entry.groupName].raw[dbidx];
+ var latlng = overviewer.util.fromWorldToLatLng(db.x, db.y, db.z, obj);
+ console.log(latlng);
+ marker_group.addLayer(new L.marker(latlng, {
+ icon: icon
+ }));
+ }
+ obj.marker_groups[marker_entry.displayName] = marker_group;
+ }
+
+
+ //var latlng = overviewer.util.fromWorldToLatLng(
+ // ovconf.spawn[0],
+ // ovconf.spawn[1],
+ // ovconf.spawn[2],
+ // obj);
+ //marker_group.addLayer(L.marker(
+ }
+ }
+
+ myLayer["tileSetConfig"] = obj;
+
+
+ if (typeof(obj.spawn) == "object") {
+ var latlng = overviewer.util.fromWorldToLatLng(obj.spawn[0], obj.spawn[1], obj.spawn[2], obj);
+ overviewer.collections.centers[obj.world] = [ latlng, 1 ];
+ } else {
+ overviewer.collections.centers[obj.world] = [ [0, 0], 1 ];
+ }
+
+ });
+
+ overviewer.layerCtrl = L.control.layers(
+ overviewer.collections.mapTypes[overviewerConfig.worlds[0]],
+ overviewer.collections.overlays[overviewerConfig.worlds[0]],
+ {collapsed: false})
+ .addTo(overviewer.map);
+ overviewer.current_world = overviewerConfig.worlds[0];
+
+ //myLayer.addTo(overviewer.map);
+ overviewer.map.setView(overviewer.util.fromWorldToLatLng(tset.spawn[0], tset.spawn[1], tset.spawn[2], tset), 1);
+
+ if (!overviewer.util.initHash()) {
+ overviewer.worldCtrl.onChange({target: {value: overviewer.current_world}});
+ }
+
+
},
'injectMarkerScript': function(url) {
@@ -180,10 +331,32 @@ overviewer.util = {
},
'initializeMarkers': function() {
+ if (overviewer.collections.haveSigns=true) {
+ console.log("initializeMarkers");
+
+
+ //Object.keys(
+ //
+ }
return;
},
+ /** Any polyfills needed to improve browser compatibility
+ */
+ 'initializePolyfills': function() {
+ // From https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
+ // IE is missing this
+ if (!('remove' in Element.prototype)) {
+ Element.prototype.remove = function() {
+ if (this.parentNode) {
+ this.parentNode.removeChild(this);
+ }
+ };
+ }
+
+ },
+
/**
* This adds some methods to these classes because Javascript is stupid
@@ -262,15 +435,6 @@ overviewer.util = {
"pregQuote": function(str) {
return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
},
- /**
- * Change the map's div's background color according to the mapType's bg_color setting
- *
- * @param string mapTypeId
- * @return string
- */
- 'getMapTypeBackgroundColor': function(id) {
- return overviewerConfig.tilesets[id].bgcolor;
- },
/**
* Gee, I wonder what this does.
*
@@ -316,10 +480,10 @@ overviewer.util = {
*
* @return google.maps.LatLng
*/
- 'fromWorldToLatLng': function(x, y, z, model) {
+ 'fromWorldToLatLng': function(x, y, z, tset) {
- var zoomLevels = model.get("zoomLevels");
- var north_direction = model.get('north_direction');
+ var zoomLevels = tset.zoomLevels;
+ var north_direction = tset.north_direction;
// the width and height of all the highest-zoom tiles combined,
// inverted
@@ -367,7 +531,7 @@ overviewer.util = {
// add on 12 px to the X coordinate to center our point
lng += 12 * perPixel;
- return new google.maps.LatLng(lat, lng);
+ return [-lat*overviewerConfig.CONST.tileSize, lng*overviewerConfig.CONST.tileSize]
},
/**
* The opposite of fromWorldToLatLng
@@ -379,9 +543,15 @@ overviewer.util = {
*
* @return Array
*/
- 'fromLatLngToWorld': function(lat, lng, model) {
- var zoomLevels = model.get("zoomLevels");
- var north_direction = model.get("north_direction");
+ 'fromLatLngToWorld': function(lat, lng, tset) {
+ var zoomLevels = tset.zoomLevels;
+ var north_direction = tset.north_direction;
+
+ lat = -lat/overviewerConfig.CONST.tileSize;
+ lng = lng/overviewerConfig.CONST.tileSize;
+
+ // lat lng will always be between (0,0) -- top left corner
+ // (-384, 384) -- bottom right corner
// Initialize world x/y/z object to be returned
var point = Array();
@@ -482,52 +652,42 @@ overviewer.util = {
overviewer.util.goToHash();
// Clean up the hash.
overviewer.util.updateHash();
+ return true;
+ } else {
+ return false; // signal to caller that we didn't goto any hash
}
}
},
'setHash': function(x, y, z, zoom, w, maptype) {
// save this info is a nice easy to parse format
- var currentWorldView = overviewer.mapModel.get("currentWorldView");
- currentWorldView.options.lastViewport = [x,y,z,zoom];
- var newHash = "#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + w + "/" + maptype;
+ var newHash = "#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + encodeURI(w) + "/" + encodeURI(maptype);
overviewer.util.lastHash = newHash; // this should not trigger initHash
window.location.replace(newHash);
},
'updateHash': function() {
- var currTileset = overviewer.mapView.options.currentTileSet;
+ // name of current world
+ var currWorld = overviewer.current_world;
+ if (currWorld == null) {return;}
+
+ var currTileset = overviewer.current_layer[currWorld];
if (currTileset == null) {return;}
- var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(),
- overviewer.map.getCenter().lng(),
- currTileset);
+
+ var ovconf = currTileset.tileSetConfig;
+
+ var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat,
+ overviewer.map.getCenter().lng,
+ ovconf);
var zoom = overviewer.map.getZoom();
- var maptype = overviewer.map.getMapTypeId();
- // convert mapType into a index
- var currentWorldView = overviewer.mapModel.get("currentWorldView");
- var maptypeId = -1;
- for (id in currentWorldView.options.mapTypeIds) {
- if (currentWorldView.options.mapTypeIds[id] == maptype) {
- maptypeId = id;
- }
- }
-
- var worldId = -1;
- for (id in overviewer.collections.worldViews) {
- if (overviewer.collections.worldViews[id] == currentWorldView) {
- worldId = id;
- }
- }
-
-
- if (zoom >= currTileset.get('maxZoom')) {
+ if (zoom >= ovconf.maxZoom) {
zoom = 'max';
- } else if (zoom <= currTileset.get('minZoom')) {
+ } else if (zoom <= ovconf.minZoom) {
zoom = 'min';
} else {
// default to (map-update friendly) negative zooms
- zoom -= currTileset.get('maxZoom');
+ zoom -= ovconf.maxZoom;
}
- overviewer.util.setHash(coordinates.x, coordinates.y, coordinates.z, zoom, worldId, maptypeId);
+ overviewer.util.setHash(coordinates.x, coordinates.y, coordinates.z, zoom, currWorld, ovconf.name);
},
'goToHash': function() {
// Note: the actual data begins at coords[1], coords[0] is empty.
@@ -535,52 +695,104 @@ overviewer.util = {
var zoom;
- var worldid = -1;
- var maptyped = -1;
+ var world_name = null;
+ var tileset_name = null;
// The if-statements try to prevent unexpected behaviour when using incomplete hashes, e.g. older links
if (coords.length > 4) {
zoom = coords[4];
}
if (coords.length > 6) {
- worldid = coords[5];
- maptypeid = coords[6];
+ world_name = decodeURI(coords[5]);
+ tileset_name = decodeURI(coords[6]);
}
- var worldView = overviewer.collections.worldViews[worldid];
- overviewer.mapModel.set({currentWorldView: worldView});
- var maptype = worldView.options.mapTypeIds[maptypeid];
- overviewer.map.setMapTypeId(maptype);
- var tsetModel = worldView.model.get("tileSets").at(maptypeid);
-
+ var target_layer = overviewer.collections.mapTypes[world_name][tileset_name];
+ var ovconf = target_layer.tileSetConfig;
+
var latlngcoords = overviewer.util.fromWorldToLatLng(parseInt(coords[1]),
parseInt(coords[2]),
parseInt(coords[3]),
- tsetModel);
+ ovconf);
if (zoom == 'max') {
- zoom = tsetModel.get('maxZoom');
+ zoom = ovconf.maxZoom;
} else if (zoom == 'min') {
- zoom = tsetModel.get('minZoom');
+ zoom = ovconf.minZoom;
} else {
zoom = parseInt(zoom);
if (zoom < 0) {
// if zoom is negative, treat it as a "zoom out from max"
- zoom += tsetModel.get('maxZoom');
+ zoom += ovconf.maxZoom;
} else {
// fall back to default zoom
- zoom = tsetModel.get('defaultZoom');
+ zoom = ovconf.defaultZoom;
}
}
// clip zoom
- if (zoom > tsetModel.get('maxZoom'))
- zoom = tsetModel.get('maxZoom');
- if (zoom < tsetModel.get('minZoom'))
- zoom = tsetModel.get('minZoom');
+ if (zoom > ovconf.maxZoom)
+ zoom = ovconf.maxZoom;
+ if (zoom < ovconf.minZoom)
+ zoom = ovconf.minZoom;
- overviewer.map.setCenter(latlngcoords);
- overviewer.map.setZoom(zoom);
- var locationmarker = new overviewer.views.LocationIconView();
- locationmarker.render();
+ // build a fake event for the world switcher control
+ overviewer.worldCtrl.onChange({target: {value: world_name}});
+ overviewer.worldCtrl.select.value = world_name;
+ if (!overviewer.map.hasLayer(target_layer)) {
+ overviewer.map.addLayer(target_layer);
+ }
+
+ overviewer.map.setView(latlngcoords, zoom);
+
+ if (ovconf.showlocationmarker) {
+ var locationIcon = L.icon({
+ iconUrl: overviewerConfig.CONST.image.queryMarker,
+ iconRetinaUrl: overviewerConfig.CONST.image.queryMarker2x,
+ iconSize: [32, 37],
+ iconAnchor: [15, 33],
+ });
+ var locationm = L.marker(latlngcoords, { icon: locationIcon,
+ title: "Linked location"});
+ overviewer.collections.locationMarker = locationm
+ overviewer.collections.locationMarker.on('contextmenu', function(ev) {
+ overviewer.collections.locationMarker.remove();
+ });
+ overviewer.collections.locationMarker.on('click', function(ev) {
+ overviewer.map.setView(ev.latlng);
+ });
+ overviewer.collections.locationMarker.addTo(overviewer.map);
+ }
+ },
+ /**
+ * Generate a function to get the path to a tile at a particular location
+ * and zoom level.
+ *
+ * @param string path
+ * @param string pathBase
+ * @param string pathExt
+ */
+ 'getTileUrlGenerator': function(path, pathBase, pathExt) {
+ return function(o) {
+ var url = path;
+ var zoom = o.z;
+ var urlBase = ( pathBase ? pathBase : '' );
+ if(o.x < 0 || o.x >= Math.pow(2, zoom) ||
+ o.y < 0 || o.y >= Math.pow(2, zoom)) {
+ url += '/blank';
+ } else if(zoom === 0) {
+ url += '/base';
+ } else {
+ for(var z = zoom - 1; z >= 0; --z) {
+ var x = Math.floor(o.x / Math.pow(2, z)) % 2;
+ var y = Math.floor(o.y / Math.pow(2, z)) % 2;
+ url += '/' + (x + 2 * y);
+ }
+ }
+ url = url + '.' + pathExt;
+ if(typeof overviewerConfig.map.cacheTag !== 'undefined') {
+ url += '?c=' + overviewerConfig.map.cacheTag;
+ }
+ return(urlBase + url);
+ };
}
};
diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js
deleted file mode 100644
index 6259223..0000000
--- a/overviewer_core/data/js_src/views.js
+++ /dev/null
@@ -1,621 +0,0 @@
-overviewer.views= {}
-
-
-overviewer.views.WorldView = Backbone.View.extend({
- initialize: function(opts) {
- this.options.mapTypes = [];
- this.options.overlayMapTypes = [];
- this.options.mapTypeIds = [];
- this.options.overlayMapTypeIds = [];
-
- var curTileSet = this.model.get("tileSets").at(0);
- var spawn = curTileSet.get("spawn");
- if (spawn == "false") {
- var spawn = [0,64,0];
- }
- this.options.lastViewport = [spawn[0],spawn[1],spawn[2],curTileSet.get("defaultZoom")];
-
- this.model.get("tileSets").each(function(tset, index, list) {
- // ignore overlays:
- var ops = {
- getTileUrl: overviewer.gmap.getTileUrlGenerator(tset.get("path"), tset.get("base"), tset.get("imgextension")),
- 'tileSize': new google.maps.Size(
- overviewerConfig.CONST.tileSize,
- overviewerConfig.CONST.tileSize),
- 'maxZoom': tset.get("maxZoom"),
- 'minZoom': tset.get("minZoom"),
- 'isPng': (tset.get("imgextension")=="png")
- };
- var newMapType = new google.maps.ImageMapType(ops);
- newMapType.name = tset.get("name");
- newMapType.shortname = tset.get("name");
- newMapType.alt = "Minecraft " + tset.get("name") + " Map";
- newMapType.projection = new overviewer.classes.MapProjection();
- newMapType._ov_tileSet = tset;
-
- if (tset.get("isOverlay")) {
- newMapType.tiles = tset.get("tilesets");
- this.options.overlayMapTypes.push(newMapType);
- this.options.overlayMapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name"));
- } else {
- this.options.mapTypes.push(newMapType);
- this.options.mapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name"));
- }
-
- }, this);
-
- this.model.get("tileSets").each(function(tset, index, list) {
- // ignore non-overlays:
- if (!tset.get("isOverlay")) { return; };
-
- });
- }
-});
-
-
-
-overviewer.views.WorldSelectorView = Backbone.View.extend({
- initialize: function() {
- if(overviewer.collections.worldViews.length > 1) {
- $(this.el).addClass("customControl");
-
- // a div will have already been created for us, we just
- // need to register it with the google maps control
- var selectBox = document.createElement('select');
- $.each(overviewer.collections.worldViews, function(index, elem) {
- var o = document.createElement("option");
- o.value = elem.model.get("name");
- o.innerHTML = elem.model.get("name");
- if (elem.model == overviewer.mapModel.get("currentWorldView").model) {
- o.selected=true;
- }
- $(o).data("viewObj", elem);
- selectBox.appendChild(o);
-
- });
-
- this.el.appendChild(selectBox);
- overviewer.map.controls[google.maps.ControlPosition.TOP_LEFT].push(this.el);
- }
- },
- events: {
- "change select": "changeWorld"
- },
- changeWorld: function() {
- var selectObj = this.$("select")[0];
- var selectedOption = selectObj.options[selectObj.selectedIndex];
-
- overviewer.mapModel.set({currentWorldView: $(selectedOption).data("viewObj")});
- //
- },
- render: function(t) {
- //console.log("WorldSelectorView::render() TODO implement this (low priority)");
- }
-});
-
-
-
-overviewer.views.CompassView = Backbone.View.extend({
- initialize: function() {
- this.el.index=0;
- var compassImg = document.createElement('IMG');
- compassImg.src = ''; // this will be set properly in the render function (below)
- this.el.appendChild(compassImg);
-
- overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el);
- },
- /**
- * CompassView::render
- */
- render: function() {
- var tsetModel = overviewer.mapView.options.currentTileSet;
- var northdir = tsetModel.get("north_direction");
- if (northdir == overviewerConfig.CONST.UPPERLEFT)
- this.$("IMG").attr("src","compass_upper-left.png");
- if (northdir == overviewerConfig.CONST.UPPERRIGHT)
- this.$("IMG").attr("src", "compass_upper-right.png");
- if (northdir == overviewerConfig.CONST.LOWERLEFT)
- this.$("IMG").attr("src", "compass_lower-left.png");
- if (northdir == overviewerConfig.CONST.LOWERRIGHT)
- this.$("IMG").attr("src", "compass_lower-right.png");
- }
-});
-
-
-overviewer.views.CoordboxView = Backbone.View.extend({
- initialize: function() {
- // Coords box
- this.el.id = 'coordsDiv';
- this.el.innerHTML = 'coords here';
- overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(this.el);
- },
- updateCoords: function(latLng) {
- var worldcoords = overviewer.util.fromLatLngToWorld(latLng.lat(),
- latLng.lng(),
- overviewer.mapView.options.currentTileSet);
-
- var regionfileX = Math.floor(Math.floor(worldcoords.x / 16.0) / 32.0);
- var regionfileZ = Math.floor(Math.floor(worldcoords.z / 16.0) / 32.0);
- var regionfilename = "r." + regionfileX + "." + regionfileZ + ".mca";
-
- this.el.innerHTML = "X " + Math.round(worldcoords.x) +
- " Z " + Math.round(worldcoords.z) +
- " (" + regionfilename + ")";
- }
-});
-
-overviewer.views.ProgressView = Backbone.View.extend({
- initialize: function() {
- this.el.id = 'progressDiv';
- this.el.innerHTML = 'Current Render Progress';
- overviewer.map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push(this.el);
- $(this.el).hide();
- $.ajaxSetup({cache: false});
- },
- updateProgress: function() {
- e = this;
- $.getJSON('progress.json', null, function(d){
- if (!(d == null||d=='')) {
- $(e.el).show();
- e.el.innerHTML = d['message'];
- if (d.update > 0) {
- setTimeout("e.updateProgress()", d.update);
- } else {
- setTimeout("e.updateProgress()", 60000);
- e.el.innerHTML="Hidden - d.update < 0";
- $(e.el).hide();
- }
- } else {
- e.el.innerHTML="Hidden - !!d==false";
- $(e.el).hide();
- }
- });
- }
-});
-
-/* GoogleMapView is responsible for dealing with the GoogleMaps API to create the
- */
-
-overviewer.views.GoogleMapView = Backbone.View.extend({
- initialize: function(opts) {
- this.options.map = null;
- var curWorld = this.model.get("currentWorldView").model;
-
- var curTset = curWorld.get("tileSets").at(0);
- var spawn = curTset.get("spawn");
- if (spawn == "false") {
- var spawn = [0,64,0];
- }
- var mapcenter = overviewer.util.fromWorldToLatLng(
- spawn[0],
- spawn[1],
- spawn[2],
- curTset);
-
-
- this.options.mapTypes=[];
- this.options.mapTypeIds=[];
- var opts = this.options;
-
- var mapOptions = {};
- //
- // init the map with some default options. use the first tileset in the first world
- this.options.mapOptions = {
- zoom: curTset.get("defaultZoom"),
- center: mapcenter,
- panControl: true,
- scaleControl: false,
- mapTypeControl: true,
- //mapTypeControlOptions: {
- //mapTypeIds: this.options.mapTypeIds
- //},
- mapTypeId: '',
- streetViewControl: false,
- overviewMapControl: true,
- zoomControl: true,
- backgroundColor: curTset.get("bgcolor")
- };
-
-
- overviewer.map = new google.maps.Map(this.el, this.options.mapOptions);
-
- // register every ImageMapType with the map
- $.each(overviewer.collections.worldViews, function( index, worldView) {
- $.each(worldView.options.mapTypes, function(i_index, maptype) {
- overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId +
- worldView.model.get("name") + maptype.shortname , maptype);
- });
- });
-
- },
- /* GoogleMapView::render()
- * Should be called when the current world has changed in GoogleMapModel
- */
- render: function() {
- var view = this.model.get("currentWorldView");
- this.options.mapOptions.mapTypeControlOptions = {
- mapTypeIds: view.options.mapTypeIds};
- this.options.mapOptions.mapTypeId = view.options.mapTypeIds[0];
- overviewer.map.setOptions(this.options.mapOptions);
-
-
- return this;
- },
- /**
- * GoogleMapView::updateCurrentTileset()
- * Keeps track of the currently visible tileset
- */
- updateCurrentTileset: function() {
- var currentWorldView = this.model.get("currentWorldView");
- var gmapCurrent = overviewer.map.getMapTypeId();
- for (id in currentWorldView.options.mapTypeIds) {
- if (currentWorldView.options.mapTypeIds[id] == gmapCurrent) {
- this.options.currentTileSet = currentWorldView.options.mapTypes[id]._ov_tileSet;
- }
- }
-
- // for this world, remember our current viewport (as worldcoords, not LatLng)
- //
-
- }
-
-});
-
-
-/**
- * OverlayControlView
- */
-overviewer.views.OverlayControlView = Backbone.View.extend({
- /** OverlayControlVIew::initialize
- */
- initialize: function(opts) {
- $(this.el).addClass("customControl");
- overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el);
- },
- registerEvents: function(me) {
- overviewer.mapModel.bind("change:currentWorldView", me.render, me);
- },
-
- /**
- * OverlayControlView::render
- */
- render: function() {
- this.el.innerHTML="";
-
- // hide all visible overlays:
- overviewer.map.overlayMapTypes.clear()
-
- // if this world has no overlays, don't create this control
- var mapTypes = overviewer.mapModel.get('currentWorldView').options.overlayMapTypes;
- if (mapTypes.length == 0) { return; }
-
- var controlText = document.createElement('DIV');
- controlText.innerHTML = "Overlays";
-
- var controlBorder = document.createElement('DIV');
- $(controlBorder).addClass('top');
- this.el.appendChild(controlBorder);
- controlBorder.appendChild(controlText);
-
- var dropdownDiv = document.createElement('DIV');
- $(dropdownDiv).addClass('dropDown');
- this.el.appendChild(dropdownDiv);
- dropdownDiv.innerHTML='';
-
- $(controlText).click(function() {
- $(controlBorder).toggleClass('top-active');
- $(dropdownDiv).toggle();
- });
-
- var currentTileSetPath = overviewer.mapView.options.currentTileSet.get('path');
-
- for (i in mapTypes) {
- var mt = mapTypes[i];
- // if this overlay specifies a list of valid tilesets, then skip over any invalid tilesets
- if ((mt.tiles.length > 0) && (mt.tiles.indexOf(currentTileSetPath) ==-1)) {
- continue;
- }
- this.addItem({label: mt.name,
- name: mt.name,
- mt: mt,
-
- action: function(this_item, checked) {
- if (checked) {
- overviewer.map.overlayMapTypes.push(this_item.mt);
- } else {
- var idx_to_delete = -1;
- overviewer.map.overlayMapTypes.forEach(function(e, j) {
- if (e == this_item.mt) {
- idx_to_delete = j;
- }
- });
- if (idx_to_delete >= 0) {
- overviewer.map.overlayMapTypes.removeAt(idx_to_delete);
- }
- }
-
- }
- });
- }
-
-
- },
-
- addItem: function(item) {
- var itemDiv = document.createElement('div');
- var itemInput = document.createElement('input');
- itemInput.type='checkbox';
-
- // if this overlay is already visible, set the checkbox
- // to checked
- overviewer.map.overlayMapTypes.forEach(function(e, j) {
- if (e == item.mt) {
- itemInput.checked=true;
- }
- });
-
- // give it a name
- $(itemInput).attr("_mc_overlayname", item.name);
- jQuery(itemInput).click((function(local_item) {
- return function(e) {
- item.action(local_item, e.target.checked);
- };
- })(item));
-
- this.$(".dropDown")[0].appendChild(itemDiv);
- itemDiv.appendChild(itemInput);
- var textNode = document.createElement('text');
- textNode.innerHTML = item.label + ' ';
-
- itemDiv.appendChild(textNode);
- }
-});
-
-
-/**
- * SignControlView
- */
-overviewer.views.SignControlView = Backbone.View.extend({
- /** SignControlView::initialize
- */
- initialize: function(opts) {
- $(this.el).addClass("customControl");
- overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el);
-
- },
- registerEvents: function(me) {
- google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function(event) {
- overviewer.mapView.updateCurrentTileset();
-
- // workaround IE issue. bah!
- if (typeof markers=="undefined") { return; }
- me.render();
-
-
- // hide markers that are part of other tilesets than this
- // for each markerSet, check:
- // if the markerSet isnot part of this tileset, hide all of the markers
- var curMarkerSet = overviewer.mapView.options.currentTileSet.get("path");
- var dataRoot = markers[curMarkerSet];
-
- jQuery.each(markers, function(key, markerSet) {
- if (key != curMarkerSet) {
- jQuery.each(markerSet, function(i, markerGroup) {
- if (typeof markerGroup.markerObjs != "undefined") {
- jQuery.each(markerGroup.markerObjs, function(j, markerObj) {
- markerObj.setVisible(false);
- });
- }
- });
- }
- });
-
- return;
-
- });
-
- },
- /**
- * SignControlView::render
- */
- render: function() {
-
- var curMarkerSet = overviewer.mapView.options.currentTileSet.get("path");
- //var dataRoot = overviewer.collections.markerInfo[curMarkerSet];
- var dataRoot = markers[curMarkerSet];
-
- this.el.innerHTML="";
-
- // if we have no markerSets for this tileset, do nothing:
- if (!dataRoot) { return; }
-
-
- var controlText = document.createElement('DIV');
- controlText.innerHTML = overviewer.mapView.options.currentTileSet.get("poititle");
-
- var controlBorder = document.createElement('DIV');
- $(controlBorder).addClass('top');
- this.el.appendChild(controlBorder);
- controlBorder.appendChild(controlText);
-
- var dropdownDiv = document.createElement('DIV');
- $(dropdownDiv).addClass('dropDown');
- this.el.appendChild(dropdownDiv);
- dropdownDiv.innerHTML='';
-
- // add the functionality to toggle visibility of the items
- $(controlText).click(function() {
- $(controlBorder).toggleClass('top-active');
- $(dropdownDiv).toggle();
- });
-
-
-
- //dataRoot['markers'] = [];
- //
- for (i in dataRoot) {
- var groupName = dataRoot[i].groupName;
- if (!dataRoot[i].created) {
- dataRoot[i].markerObjs = [];
- for (j in markersDB[groupName].raw) {
- var entity = markersDB[groupName].raw[j];
- if (entity['icon']) {
- iconURL = entity['icon'];
- } else {
- iconURL = dataRoot[i].icon;
- }
- var marker = new google.maps.Marker({
- 'position': overviewer.util.fromWorldToLatLng(entity.x,
- entity.y, entity.z, overviewer.mapView.options.currentTileSet),
- 'map': overviewer.map,
- 'title': jQuery.trim(entity.hovertext),
- 'content': jQuery.trim(entity.text),
- 'icon': iconURL,
- 'visible': false
- });
- if(entity['createInfoWindow'] == true) {
- overviewer.util.createMarkerInfoWindow(marker);
- } else {
- if(dataRoot[i].createInfoWindow == true) {
- overviewer.util.createMarkerInfoWindow(marker);
- }
- }
- dataRoot[i].markerObjs.push(marker);
- // Polyline stuff added by FreakusGeekus. Probably needs work.
- if (typeof entity['polyline'] != 'undefined') {
- var polypath = new Array();
- for (point in entity.polyline) {
- polypath.push(overviewer.util.fromWorldToLatLng(entity.polyline[point].x, entity.polyline[point].y, entity.polyline[point].z, overviewer.mapView.options.currentTileSet));
- }
-
- var polyline = new google.maps.Polyline({
- 'path': polypath,
- 'clickable': false,
- 'map': overviewer.map,
- 'visible': false,
- 'strokeColor': entity['strokeColor']
- });
- dataRoot[i].markerObjs.push(polyline);
- }
-
- // Polygons
- if (typeof entity['polygon'] != 'undefined') {
- var polypath = new Array();
- for (point in entity.polygon) {
- polypath.push(overviewer.util.fromWorldToLatLng(entity.polygon[point].x, entity.polygon[point].y, entity.polygon[point].z, overviewer.mapView.options.currentTileSet));
- }
-
- var polygon = new google.maps.Polygon({
- 'clickable': false,
- 'fillColor': entity['fillColor'],
- 'fillOpacity': entity['fillOpacity'],
- 'map': overviewer.map,
- 'path': polypath,
- 'strokeColor': entity['strokeColor'],
- 'strokeOpacity': entity['strokeOpacity'],
- 'visible': false
- });
- dataRoot[i].markerObjs.push(polygon);
- }
- }
- dataRoot[i].created = true;
- }
- }
-
- // add some menus
- for (i in dataRoot) {
- var group = dataRoot[i];
- this.addItem({group: group, action:function(this_item, checked) {
- this_item.group.checked = checked;
- jQuery.each(this_item.group.markerObjs, function(i, markerObj) {
- markerObj.setVisible(checked);
- });
- }});
- if (group.checked) {
- jQuery.each(group.markerObjs, function(i, markerObj) {
- markerObj.setVisible(true);
- });
- }
- }
-
-
- },
- addItem: function(item) {
- var itemDiv = document.createElement('div');
- var itemInput = document.createElement('input');
- itemInput.type='checkbox';
-
- if (item.group.checked) {
- itemInput.checked="true";
- }
-
- // give it a name
- $(itemInput).data('label',item.group.displayName);
- $(itemInput).attr("_mc_groupname", item.group.gropuName);
- jQuery(itemInput).click((function(local_item) {
- return function(e) {
- item.action(local_item, e.target.checked);
- };
- })(item));
-
- this.$(".dropDown")[0].appendChild(itemDiv);
- itemDiv.appendChild(itemInput);
- var textNode = document.createElement('text');
- if(item.icon) {
- textNode.innerHTML = '' + item.group.displayName + ' ';
- } else {
- textNode.innerHTML = item.group.displayName + ' ';
- }
-
- itemDiv.appendChild(textNode);
- itemDiv.style.whiteSpace = "nowrap";
-
- }
-});
-
-/**
- * SpawnIconView
- */
-overviewer.views.SpawnIconView = Backbone.View.extend({
- render: function() {
- //
- var curTileSet = overviewer.mapView.options.currentTileSet;
- if (overviewer.collections.spawnMarker) {
- overviewer.collections.spawnMarker.setMap(null);
- overviewer.collections.spawnMarker = null;
- }
- var spawn = curTileSet.get("spawn");
- if (spawn) {
- overviewer.collections.spawnMarker = new google.maps.Marker({
- 'position': overviewer.util.fromWorldToLatLng(spawn[0],
- spawn[1], spawn[2], overviewer.mapView.options.currentTileSet),
- 'map': overviewer.map,
- 'title': 'spawn',
- 'icon': overviewerConfig.CONST.image.spawnMarker,
- 'visible': false
- });
- overviewer.collections.spawnMarker.setVisible(true);
- }
- }
-});
-
-overviewer.views.LocationIconView = Backbone.View.extend({
- render: function() {
- //
- if (overviewer.collections.locationMarker) {
- overviewer.collections.locationMarker.setMap(null);
- overviewer.collections.locationMarker = null;
- }
- overviewer.collections.locationMarker = new google.maps.Marker({
- 'position': overviewer.map.getCenter(),
- 'map': overviewer.map,
- 'title': 'location',
- 'icon': overviewerConfig.CONST.image.queryMarker,
- 'visible': false
- });
- overviewer.collections.locationMarker.setVisible(overviewer.mapView.options.currentTileSet.get("showlocationmarker"));
-
- }
-});
-
diff --git a/overviewer_core/data/textures/icon_src/marker_home.svg b/overviewer_core/data/textures/icon_src/marker_home.svg
new file mode 100644
index 0000000..f8b2b66
--- /dev/null
+++ b/overviewer_core/data/textures/icon_src/marker_home.svg
@@ -0,0 +1,75 @@
+
+
diff --git a/overviewer_core/data/textures/icon_src/marker_location.svg b/overviewer_core/data/textures/icon_src/marker_location.svg
new file mode 100644
index 0000000..e12530b
--- /dev/null
+++ b/overviewer_core/data/textures/icon_src/marker_location.svg
@@ -0,0 +1,77 @@
+
+
diff --git a/overviewer_core/data/web_assets/backbone.js b/overviewer_core/data/web_assets/backbone.js
deleted file mode 100644
index b2e4932..0000000
--- a/overviewer_core/data/web_assets/backbone.js
+++ /dev/null
@@ -1,1158 +0,0 @@
-// Backbone.js 0.5.3
-// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
-// Backbone may be freely distributed under the MIT license.
-// For all details and documentation:
-// http://documentcloud.github.com/backbone
-
-(function(){
-
- // Initial Setup
- // -------------
-
- // Save a reference to the global object.
- var root = this;
-
- // Save the previous value of the `Backbone` variable.
- var previousBackbone = root.Backbone;
-
- // The top-level namespace. All public Backbone classes and modules will
- // be attached to this. Exported for both CommonJS and the browser.
- var Backbone;
- if (typeof exports !== 'undefined') {
- Backbone = exports;
- } else {
- Backbone = root.Backbone = {};
- }
-
- // Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '0.5.3';
-
- // Require Underscore, if we're on the server, and it's not already present.
- var _ = root._;
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
-
- // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
- var $ = root.jQuery || root.Zepto;
-
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
- // to its previous owner. Returns a reference to this Backbone object.
- Backbone.noConflict = function() {
- root.Backbone = previousBackbone;
- return this;
- };
-
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
- // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
- // `X-Http-Method-Override` header.
- Backbone.emulateHTTP = false;
-
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
- // `application/json` requests ... will encode the body as
- // `application/x-www-form-urlencoded` instead and will send the model in a
- // form param named `model`.
- Backbone.emulateJSON = false;
-
- // Backbone.Events
- // -----------------
-
- // A module that can be mixed in to *any object* in order to provide it with
- // custom events. You may `bind` or `unbind` a callback function to an event;
- // `trigger`-ing an event fires all callbacks in succession.
- //
- // var object = {};
- // _.extend(object, Backbone.Events);
- // object.bind('expand', function(){ alert('expanded'); });
- // object.trigger('expand');
- //
- Backbone.Events = {
-
- // Bind an event, specified by a string name, `ev`, to a `callback` function.
- // Passing `"all"` will bind the callback to all events fired.
- bind : function(ev, callback, context) {
- var calls = this._callbacks || (this._callbacks = {});
- var list = calls[ev] || (calls[ev] = []);
- list.push([callback, context]);
- return this;
- },
-
- // Remove one or many callbacks. If `callback` is null, removes all
- // callbacks for the event. If `ev` is null, removes all bound callbacks
- // for all events.
- unbind : function(ev, callback) {
- var calls;
- if (!ev) {
- this._callbacks = {};
- } else if (calls = this._callbacks) {
- if (!callback) {
- calls[ev] = [];
- } else {
- var list = calls[ev];
- if (!list) return this;
- for (var i = 0, l = list.length; i < l; i++) {
- if (list[i] && callback === list[i][0]) {
- list[i] = null;
- break;
- }
- }
- }
- }
- return this;
- },
-
- // Trigger an event, firing all bound callbacks. Callbacks are passed the
- // same arguments as `trigger` is, apart from the event name.
- // Listening for `"all"` passes the true event name as the first argument.
- trigger : function(eventName) {
- var list, calls, ev, callback, args;
- var both = 2;
- if (!(calls = this._callbacks)) return this;
- while (both--) {
- ev = both ? eventName : 'all';
- if (list = calls[ev]) {
- for (var i = 0, l = list.length; i < l; i++) {
- if (!(callback = list[i])) {
- list.splice(i, 1); i--; l--;
- } else {
- args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
- callback[0].apply(callback[1] || this, args);
- }
- }
- }
- }
- return this;
- }
-
- };
-
- // Backbone.Model
- // --------------
-
- // Create a new model, with defined attributes. A client id (`cid`)
- // is automatically generated and assigned for you.
- Backbone.Model = function(attributes, options) {
- var defaults;
- attributes || (attributes = {});
- if (defaults = this.defaults) {
- if (_.isFunction(defaults)) defaults = defaults.call(this);
- attributes = _.extend({}, defaults, attributes);
- }
- this.attributes = {};
- this._escapedAttributes = {};
- this.cid = _.uniqueId('c');
- this.set(attributes, {silent : true});
- this._changed = false;
- this._previousAttributes = _.clone(this.attributes);
- if (options && options.collection) this.collection = options.collection;
- this.initialize(attributes, options);
- };
-
- // Attach all inheritable methods to the Model prototype.
- _.extend(Backbone.Model.prototype, Backbone.Events, {
-
- // A snapshot of the model's previous attributes, taken immediately
- // after the last `"change"` event was fired.
- _previousAttributes : null,
-
- // Has the item been changed since the last `"change"` event?
- _changed : false,
-
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
- // CouchDB users may want to set this to `"_id"`.
- idAttribute : 'id',
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // Return a copy of the model's `attributes` object.
- toJSON : function() {
- return _.clone(this.attributes);
- },
-
- // Get the value of an attribute.
- get : function(attr) {
- return this.attributes[attr];
- },
-
- // Get the HTML-escaped value of an attribute.
- escape : function(attr) {
- var html;
- if (html = this._escapedAttributes[attr]) return html;
- var val = this.attributes[attr];
- return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
- },
-
- // Returns `true` if the attribute contains a value that is not null
- // or undefined.
- has : function(attr) {
- return this.attributes[attr] != null;
- },
-
- // Set a hash of model attributes on the object, firing `"change"` unless you
- // choose to silence it.
- set : function(attrs, options) {
-
- // Extract attributes and options.
- options || (options = {});
- if (!attrs) return this;
- if (attrs.attributes) attrs = attrs.attributes;
- var now = this.attributes, escaped = this._escapedAttributes;
-
- // Run validation.
- if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
-
- // Check for changes of `id`.
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
-
- // We're about to start triggering change events.
- var alreadyChanging = this._changing;
- this._changing = true;
-
- // Update attributes.
- for (var attr in attrs) {
- var val = attrs[attr];
- if (!_.isEqual(now[attr], val)) {
- now[attr] = val;
- delete escaped[attr];
- this._changed = true;
- if (!options.silent) this.trigger('change:' + attr, this, val, options);
- }
- }
-
- // Fire the `"change"` event, if the model has been changed.
- if (!alreadyChanging && !options.silent && this._changed) this.change(options);
- this._changing = false;
- return this;
- },
-
- // Remove an attribute from the model, firing `"change"` unless you choose
- // to silence it. `unset` is a noop if the attribute doesn't exist.
- unset : function(attr, options) {
- if (!(attr in this.attributes)) return this;
- options || (options = {});
- var value = this.attributes[attr];
-
- // Run validation.
- var validObj = {};
- validObj[attr] = void 0;
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
-
- // Remove the attribute.
- delete this.attributes[attr];
- delete this._escapedAttributes[attr];
- if (attr == this.idAttribute) delete this.id;
- this._changed = true;
- if (!options.silent) {
- this.trigger('change:' + attr, this, void 0, options);
- this.change(options);
- }
- return this;
- },
-
- // Clear all attributes on the model, firing `"change"` unless you choose
- // to silence it.
- clear : function(options) {
- options || (options = {});
- var attr;
- var old = this.attributes;
-
- // Run validation.
- var validObj = {};
- for (attr in old) validObj[attr] = void 0;
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
-
- this.attributes = {};
- this._escapedAttributes = {};
- this._changed = true;
- if (!options.silent) {
- for (attr in old) {
- this.trigger('change:' + attr, this, void 0, options);
- }
- this.change(options);
- }
- return this;
- },
-
- // Fetch the model from the server. If the server's representation of the
- // model differs from its current attributes, they will be overriden,
- // triggering a `"change"` event.
- fetch : function(options) {
- options || (options = {});
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp);
- };
- options.error = wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Set a hash of model attributes, and sync the model to the server.
- // If the server returns an attributes hash that differs, the model's
- // state will be `set` again.
- save : function(attrs, options) {
- options || (options = {});
- if (attrs && !this.set(attrs, options)) return false;
- var model = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp, xhr);
- };
- options.error = wrapError(options.error, model, options);
- var method = this.isNew() ? 'create' : 'update';
- return (this.sync || Backbone.sync).call(this, method, this, options);
- },
-
- // Destroy this model on the server if it was already persisted. Upon success, the model is removed
- // from its collection, if it has one.
- destroy : function(options) {
- options || (options = {});
- if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
- var model = this;
- var success = options.success;
- options.success = function(resp) {
- model.trigger('destroy', model, model.collection, options);
- if (success) success(model, resp);
- };
- options.error = wrapError(options.error, model, options);
- return (this.sync || Backbone.sync).call(this, 'delete', this, options);
- },
-
- // Default URL for the model's representation on the server -- if you're
- // using Backbone's restful methods, override this to change the endpoint
- // that will be called.
- url : function() {
- var base = getUrl(this.collection) || this.urlRoot || urlError();
- if (this.isNew()) return base;
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
- },
-
- // **parse** converts a response into the hash of attributes to be `set` on
- // the model. The default implementation is just to pass the response along.
- parse : function(resp, xhr) {
- return resp;
- },
-
- // Create a new model with identical attributes to this one.
- clone : function() {
- return new this.constructor(this);
- },
-
- // A model is new if it has never been saved to the server, and lacks an id.
- isNew : function() {
- return this.id == null;
- },
-
- // Call this method to manually fire a `change` event for this model.
- // Calling this will cause all objects observing the model to update.
- change : function(options) {
- this.trigger('change', this, options);
- this._previousAttributes = _.clone(this.attributes);
- this._changed = false;
- },
-
- // Determine if the model has changed since the last `"change"` event.
- // If you specify an attribute name, determine if that attribute has changed.
- hasChanged : function(attr) {
- if (attr) return this._previousAttributes[attr] != this.attributes[attr];
- return this._changed;
- },
-
- // Return an object containing all the attributes that have changed, or false
- // if there are no changed attributes. Useful for determining what parts of a
- // view need to be updated and/or what attributes need to be persisted to
- // the server.
- changedAttributes : function(now) {
- now || (now = this.attributes);
- var old = this._previousAttributes;
- var changed = false;
- for (var attr in now) {
- if (!_.isEqual(old[attr], now[attr])) {
- changed = changed || {};
- changed[attr] = now[attr];
- }
- }
- return changed;
- },
-
- // Get the previous value of an attribute, recorded at the time the last
- // `"change"` event was fired.
- previous : function(attr) {
- if (!attr || !this._previousAttributes) return null;
- return this._previousAttributes[attr];
- },
-
- // Get all of the attributes of the model at the time of the previous
- // `"change"` event.
- previousAttributes : function() {
- return _.clone(this._previousAttributes);
- },
-
- // Run validation against a set of incoming attributes, returning `true`
- // if all is well. If a specific `error` callback has been passed,
- // call that instead of firing the general `"error"` event.
- _performValidation : function(attrs, options) {
- var error = this.validate(attrs);
- if (error) {
- if (options.error) {
- options.error(this, error, options);
- } else {
- this.trigger('error', this, error, options);
- }
- return false;
- }
- return true;
- }
-
- });
-
- // Backbone.Collection
- // -------------------
-
- // Provides a standard collection class for our sets of models, ordered
- // or unordered. If a `comparator` is specified, the Collection will maintain
- // its models in sort order, as they're added and removed.
- Backbone.Collection = function(models, options) {
- options || (options = {});
- if (options.comparator) this.comparator = options.comparator;
- _.bindAll(this, '_onModelEvent', '_removeReference');
- this._reset();
- if (models) this.reset(models, {silent: true});
- this.initialize.apply(this, arguments);
- };
-
- // Define the Collection's inheritable methods.
- _.extend(Backbone.Collection.prototype, Backbone.Events, {
-
- // The default model for a collection is just a **Backbone.Model**.
- // This should be overridden in most cases.
- model : Backbone.Model,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // The JSON representation of a Collection is an array of the
- // models' attributes.
- toJSON : function() {
- return this.map(function(model){ return model.toJSON(); });
- },
-
- // Add a model, or list of models to the set. Pass **silent** to avoid
- // firing the `added` event for every new model.
- add : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- this._add(models[i], options);
- }
- } else {
- this._add(models, options);
- }
- return this;
- },
-
- // Remove a model, or a list of models from the set. Pass silent to avoid
- // firing the `removed` event for every model removed.
- remove : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- this._remove(models[i], options);
- }
- } else {
- this._remove(models, options);
- }
- return this;
- },
-
- // Get a model from the set by id.
- get : function(id) {
- if (id == null) return null;
- return this._byId[id.id != null ? id.id : id];
- },
-
- // Get a model from the set by client id.
- getByCid : function(cid) {
- return cid && this._byCid[cid.cid || cid];
- },
-
- // Get the model at the given index.
- at: function(index) {
- return this.models[index];
- },
-
- // Force the collection to re-sort itself. You don't need to call this under normal
- // circumstances, as the set will maintain sort order as each item is added.
- sort : function(options) {
- options || (options = {});
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
- this.models = this.sortBy(this.comparator);
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Pluck an attribute from each model in the collection.
- pluck : function(attr) {
- return _.map(this.models, function(model){ return model.get(attr); });
- },
-
- // When you have more items than you want to add or remove individually,
- // you can reset the entire set with a new list of models, without firing
- // any `added` or `removed` events. Fires `reset` when finished.
- reset : function(models, options) {
- models || (models = []);
- options || (options = {});
- this.each(this._removeReference);
- this._reset();
- this.add(models, {silent: true});
- if (!options.silent) this.trigger('reset', this, options);
- return this;
- },
-
- // Fetch the default set of models for this collection, resetting the
- // collection when they arrive. If `add: true` is passed, appends the
- // models to the collection instead of resetting.
- fetch : function(options) {
- options || (options = {});
- var collection = this;
- var success = options.success;
- options.success = function(resp, status, xhr) {
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
- if (success) success(collection, resp);
- };
- options.error = wrapError(options.error, collection, options);
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
- },
-
- // Create a new instance of a model in this collection. After the model
- // has been created on the server, it will be added to the collection.
- // Returns the model, or 'false' if validation on a new model fails.
- create : function(model, options) {
- var coll = this;
- options || (options = {});
- model = this._prepareModel(model, options);
- if (!model) return false;
- var success = options.success;
- options.success = function(nextModel, resp, xhr) {
- coll.add(nextModel, options);
- if (success) success(nextModel, resp, xhr);
- };
- model.save(null, options);
- return model;
- },
-
- // **parse** converts a response into a list of models to be added to the
- // collection. The default implementation is just to pass it through.
- parse : function(resp, xhr) {
- return resp;
- },
-
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
- // underscore methods are proxied because it relies on the underscore
- // constructor.
- chain: function () {
- return _(this.models).chain();
- },
-
- // Reset all internal state. Called when the collection is reset.
- _reset : function(options) {
- this.length = 0;
- this.models = [];
- this._byId = {};
- this._byCid = {};
- },
-
- // Prepare a model to be added to this collection
- _prepareModel: function(model, options) {
- if (!(model instanceof Backbone.Model)) {
- var attrs = model;
- model = new this.model(attrs, {collection: this});
- if (model.validate && !model._performValidation(attrs, options)) model = false;
- } else if (!model.collection) {
- model.collection = this;
- }
- return model;
- },
-
- // Internal implementation of adding a single model to the set, updating
- // hash indexes for `id` and `cid` lookups.
- // Returns the model, or 'false' if validation on a new model fails.
- _add : function(model, options) {
- options || (options = {});
- model = this._prepareModel(model, options);
- if (!model) return false;
- var already = this.getByCid(model);
- if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
- this._byId[model.id] = model;
- this._byCid[model.cid] = model;
- var index = options.at != null ? options.at :
- this.comparator ? this.sortedIndex(model, this.comparator) :
- this.length;
- this.models.splice(index, 0, model);
- model.bind('all', this._onModelEvent);
- this.length++;
- if (!options.silent) model.trigger('add', model, this, options);
- return model;
- },
-
- // Internal implementation of removing a single model from the set, updating
- // hash indexes for `id` and `cid` lookups.
- _remove : function(model, options) {
- options || (options = {});
- model = this.getByCid(model) || this.get(model);
- if (!model) return null;
- delete this._byId[model.id];
- delete this._byCid[model.cid];
- this.models.splice(this.indexOf(model), 1);
- this.length--;
- if (!options.silent) model.trigger('remove', model, this, options);
- this._removeReference(model);
- return model;
- },
-
- // Internal method to remove a model's ties to a collection.
- _removeReference : function(model) {
- if (this == model.collection) {
- delete model.collection;
- }
- model.unbind('all', this._onModelEvent);
- },
-
- // Internal method called every time a model in the set fires an event.
- // Sets need to update their indexes when models change ids. All other
- // events simply proxy through. "add" and "remove" events that originate
- // in other collections are ignored.
- _onModelEvent : function(ev, model, collection, options) {
- if ((ev == 'add' || ev == 'remove') && collection != this) return;
- if (ev == 'destroy') {
- this._remove(model, options);
- }
- if (model && ev === 'change:' + model.idAttribute) {
- delete this._byId[model.previous(model.idAttribute)];
- this._byId[model.id] = model;
- }
- this.trigger.apply(this, arguments);
- }
-
- });
-
- // Underscore methods that we want to implement on the Collection.
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
- 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
- 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
- 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
-
- // Mix in each Underscore method as a proxy to `Collection#models`.
- _.each(methods, function(method) {
- Backbone.Collection.prototype[method] = function() {
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
- };
- });
-
- // Backbone.Router
- // -------------------
-
- // Routers map faux-URLs to actions, and fire events when routes are
- // matched. Creating a new one sets its `routes` hash, if not set statically.
- Backbone.Router = function(options) {
- options || (options = {});
- if (options.routes) this.routes = options.routes;
- this._bindRoutes();
- this.initialize.apply(this, arguments);
- };
-
- // Cached regular expressions for matching named param parts and splatted
- // parts of route strings.
- var namedParam = /:([\w\d]+)/g;
- var splatParam = /\*([\w\d]+)/g;
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
-
- // Set up all inheritable **Backbone.Router** properties and methods.
- _.extend(Backbone.Router.prototype, Backbone.Events, {
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // Manually bind a single named route to a callback. For example:
- //
- // this.route('search/:query/p:num', 'search', function(query, num) {
- // ...
- // });
- //
- route : function(route, name, callback) {
- Backbone.history || (Backbone.history = new Backbone.History);
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
- Backbone.history.route(route, _.bind(function(fragment) {
- var args = this._extractParameters(route, fragment);
- callback.apply(this, args);
- this.trigger.apply(this, ['route:' + name].concat(args));
- }, this));
- },
-
- // Simple proxy to `Backbone.history` to save a fragment into the history.
- navigate : function(fragment, triggerRoute) {
- Backbone.history.navigate(fragment, triggerRoute);
- },
-
- // Bind all defined routes to `Backbone.history`. We have to reverse the
- // order of the routes here to support behavior where the most general
- // routes can be defined at the bottom of the route map.
- _bindRoutes : function() {
- if (!this.routes) return;
- var routes = [];
- for (var route in this.routes) {
- routes.unshift([route, this.routes[route]]);
- }
- for (var i = 0, l = routes.length; i < l; i++) {
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
- }
- },
-
- // Convert a route string into a regular expression, suitable for matching
- // against the current location hash.
- _routeToRegExp : function(route) {
- route = route.replace(escapeRegExp, "\\$&")
- .replace(namedParam, "([^\/]*)")
- .replace(splatParam, "(.*?)");
- return new RegExp('^' + route + '$');
- },
-
- // Given a route, and a URL fragment that it matches, return the array of
- // extracted parameters.
- _extractParameters : function(route, fragment) {
- return route.exec(fragment).slice(1);
- }
-
- });
-
- // Backbone.History
- // ----------------
-
- // Handles cross-browser history management, based on URL fragments. If the
- // browser does not support `onhashchange`, falls back to polling.
- Backbone.History = function() {
- this.handlers = [];
- _.bindAll(this, 'checkUrl');
- };
-
- // Cached regex for cleaning hashes.
- var hashStrip = /^#*/;
-
- // Cached regex for detecting MSIE.
- var isExplorer = /msie [\w.]+/;
-
- // Has the history handling already been started?
- var historyStarted = false;
-
- // Set up all inheritable **Backbone.History** properties and methods.
- _.extend(Backbone.History.prototype, {
-
- // The default interval to poll for hash changes, if necessary, is
- // twenty times a second.
- interval: 50,
-
- // Get the cross-browser normalized URL fragment, either from the URL,
- // the hash, or the override.
- getFragment : function(fragment, forcePushState) {
- if (fragment == null) {
- if (this._hasPushState || forcePushState) {
- fragment = window.location.pathname;
- var search = window.location.search;
- if (search) fragment += search;
- if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
- } else {
- fragment = window.location.hash;
- }
- }
- return decodeURIComponent(fragment.replace(hashStrip, ''));
- },
-
- // Start the hash change handling, returning `true` if the current URL matches
- // an existing route, and `false` otherwise.
- start : function(options) {
-
- // Figure out the initial configuration. Do we need an iframe?
- // Is pushState desired ... is it available?
- if (historyStarted) throw new Error("Backbone.history has already been started");
- this.options = _.extend({}, {root: '/'}, this.options, options);
- this._wantsPushState = !!this.options.pushState;
- this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
- var fragment = this.getFragment();
- var docMode = document.documentMode;
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
- if (oldIE) {
- this.iframe = $('').hide().appendTo('body')[0].contentWindow;
- this.navigate(fragment);
- }
-
- // Depending on whether we're using pushState or hashes, and whether
- // 'onhashchange' is supported, determine how we check the URL state.
- if (this._hasPushState) {
- $(window).bind('popstate', this.checkUrl);
- } else if ('onhashchange' in window && !oldIE) {
- $(window).bind('hashchange', this.checkUrl);
- } else {
- setInterval(this.checkUrl, this.interval);
- }
-
- // Determine if we need to change the base url, for a pushState link
- // opened by a non-pushState browser.
- this.fragment = fragment;
- historyStarted = true;
- var loc = window.location;
- var atRoot = loc.pathname == this.options.root;
- if (this._wantsPushState && !this._hasPushState && !atRoot) {
- this.fragment = this.getFragment(null, true);
- window.location.replace(this.options.root + '#' + this.fragment);
- // Return immediately as browser will do redirect to new url
- return true;
- } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
- this.fragment = loc.hash.replace(hashStrip, '');
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
- }
-
- if (!this.options.silent) {
- return this.loadUrl();
- }
- },
-
- // Add a route to be tested when the fragment changes. Routes added later may
- // override previous routes.
- route : function(route, callback) {
- this.handlers.unshift({route : route, callback : callback});
- },
-
- // Checks the current URL to see if it has changed, and if it has,
- // calls `loadUrl`, normalizing across the hidden iframe.
- checkUrl : function(e) {
- var current = this.getFragment();
- if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
- if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
- if (this.iframe) this.navigate(current);
- this.loadUrl() || this.loadUrl(window.location.hash);
- },
-
- // Attempt to load the current URL fragment. If a route succeeds with a
- // match, returns `true`. If no defined routes matches the fragment,
- // returns `false`.
- loadUrl : function(fragmentOverride) {
- var fragment = this.fragment = this.getFragment(fragmentOverride);
- var matched = _.any(this.handlers, function(handler) {
- if (handler.route.test(fragment)) {
- handler.callback(fragment);
- return true;
- }
- });
- return matched;
- },
-
- // Save a fragment into the hash history. You are responsible for properly
- // URL-encoding the fragment in advance. This does not trigger
- // a `hashchange` event.
- navigate : function(fragment, triggerRoute) {
- var frag = (fragment || '').replace(hashStrip, '');
- if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
- if (this._hasPushState) {
- var loc = window.location;
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
- this.fragment = frag;
- window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
- } else {
- window.location.hash = this.fragment = frag;
- if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
- this.iframe.document.open().close();
- this.iframe.location.hash = frag;
- }
- }
- if (triggerRoute) this.loadUrl(fragment);
- }
-
- });
-
- // Backbone.View
- // -------------
-
- // Creating a Backbone.View creates its initial element outside of the DOM,
- // if an existing element is not provided...
- Backbone.View = function(options) {
- this.cid = _.uniqueId('view');
- this._configure(options || {});
- this._ensureElement();
- this.delegateEvents();
- this.initialize.apply(this, arguments);
- };
-
- // Element lookup, scoped to DOM elements within the current view.
- // This should be prefered to global lookups, if you're dealing with
- // a specific view.
- var selectorDelegate = function(selector) {
- return $(selector, this.el);
- };
-
- // Cached regex to split keys for `delegate`.
- var eventSplitter = /^(\S+)\s*(.*)$/;
-
- // List of view options to be merged as properties.
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
-
- // Set up all inheritable **Backbone.View** properties and methods.
- _.extend(Backbone.View.prototype, Backbone.Events, {
-
- // The default `tagName` of a View's element is `"div"`.
- tagName : 'div',
-
- // Attach the `selectorDelegate` function as the `$` property.
- $ : selectorDelegate,
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic.
- initialize : function(){},
-
- // **render** is the core function that your view should override, in order
- // to populate its element (`this.el`), with the appropriate HTML. The
- // convention is for **render** to always return `this`.
- render : function() {
- return this;
- },
-
- // Remove this view from the DOM. Note that the view isn't present in the
- // DOM by default, so calling this method may be a no-op.
- remove : function() {
- $(this.el).remove();
- return this;
- },
-
- // For small amounts of DOM Elements, where a full-blown template isn't
- // needed, use **make** to manufacture elements, one at a time.
- //
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
- //
- make : function(tagName, attributes, content) {
- var el = document.createElement(tagName);
- if (attributes) $(el).attr(attributes);
- if (content) $(el).html(content);
- return el;
- },
-
- // Set callbacks, where `this.callbacks` is a hash of
- //
- // *{"event selector": "callback"}*
- //
- // {
- // 'mousedown .title': 'edit',
- // 'click .button': 'save'
- // }
- //
- // pairs. Callbacks will be bound to the view, with `this` set properly.
- // Uses event delegation for efficiency.
- // Omitting the selector binds the event to `this.el`.
- // This only works for delegate-able events: not `focus`, `blur`, and
- // not `change`, `submit`, and `reset` in Internet Explorer.
- delegateEvents : function(events) {
- if (!(events || (events = this.events))) return;
- if (_.isFunction(events)) events = events.call(this);
- $(this.el).unbind('.delegateEvents' + this.cid);
- for (var key in events) {
- var method = this[events[key]];
- if (!method) throw new Error('Event "' + events[key] + '" does not exist');
- var match = key.match(eventSplitter);
- var eventName = match[1], selector = match[2];
- method = _.bind(method, this);
- eventName += '.delegateEvents' + this.cid;
- if (selector === '') {
- $(this.el).bind(eventName, method);
- } else {
- $(this.el).delegate(selector, eventName, method);
- }
- }
- },
-
- // Performs the initial configuration of a View with a set of options.
- // Keys with special meaning *(model, collection, id, className)*, are
- // attached directly to the view.
- _configure : function(options) {
- if (this.options) options = _.extend({}, this.options, options);
- for (var i = 0, l = viewOptions.length; i < l; i++) {
- var attr = viewOptions[i];
- if (options[attr]) this[attr] = options[attr];
- }
- this.options = options;
- },
-
- // Ensure that the View has a DOM element to render into.
- // If `this.el` is a string, pass it through `$()`, take the first
- // matching element, and re-assign it to `el`. Otherwise, create
- // an element from the `id`, `className` and `tagName` proeprties.
- _ensureElement : function() {
- if (!this.el) {
- var attrs = this.attributes || {};
- if (this.id) attrs.id = this.id;
- if (this.className) attrs['class'] = this.className;
- this.el = this.make(this.tagName, attrs);
- } else if (_.isString(this.el)) {
- this.el = $(this.el).get(0);
- }
- }
-
- });
-
- // The self-propagating extend function that Backbone classes use.
- var extend = function (protoProps, classProps) {
- var child = inherits(this, protoProps, classProps);
- child.extend = this.extend;
- return child;
- };
-
- // Set up inheritance for the model, collection, and view.
- Backbone.Model.extend = Backbone.Collection.extend =
- Backbone.Router.extend = Backbone.View.extend = extend;
-
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
- var methodMap = {
- 'create': 'POST',
- 'update': 'PUT',
- 'delete': 'DELETE',
- 'read' : 'GET'
- };
-
- // Backbone.sync
- // -------------
-
- // Override this function to change the manner in which Backbone persists
- // models to the server. You will be passed the type of request, and the
- // model in question. By default, uses makes a RESTful Ajax request
- // to the model's `url()`. Some possible customizations could be:
- //
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
- // * Send up the models as XML instead of JSON.
- // * Persist models via WebSockets instead of Ajax.
- //
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
- // as `POST`, with a `_method` parameter containing the true HTTP method,
- // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
- // `application/json` with the model in a param named `model`.
- // Useful when interfacing with server-side languages like **PHP** that make
- // it difficult to read the body of `PUT` requests.
- Backbone.sync = function(method, model, options) {
- var type = methodMap[method];
-
- // Default JSON-request options.
- var params = _.extend({
- type: type,
- dataType: 'json'
- }, options);
-
- // Ensure that we have a URL.
- if (!params.url) {
- params.url = getUrl(model) || urlError();
- }
-
- // Ensure that we have the appropriate request data.
- if (!params.data && model && (method == 'create' || method == 'update')) {
- params.contentType = 'application/json';
- params.data = JSON.stringify(model.toJSON());
- }
-
- // For older servers, emulate JSON by encoding the request into an HTML-form.
- if (Backbone.emulateJSON) {
- params.contentType = 'application/x-www-form-urlencoded';
- params.data = params.data ? {model : params.data} : {};
- }
-
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
- // And an `X-HTTP-Method-Override` header.
- if (Backbone.emulateHTTP) {
- if (type === 'PUT' || type === 'DELETE') {
- if (Backbone.emulateJSON) params.data._method = type;
- params.type = 'POST';
- params.beforeSend = function(xhr) {
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
- };
- }
- }
-
- // Don't process data on a non-GET request.
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
- params.processData = false;
- }
-
- // Make the request.
- return $.ajax(params);
- };
-
- // Helpers
- // -------
-
- // Shared empty constructor function to aid in prototype-chain creation.
- var ctor = function(){};
-
- // Helper function to correctly set up the prototype chain, for subclasses.
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
- // class properties to be extended.
- var inherits = function(parent, protoProps, staticProps) {
- var child;
-
- // The constructor function for the new subclass is either defined by you
- // (the "constructor" property in your `extend` definition), or defaulted
- // by us to simply call `super()`.
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
- child = protoProps.constructor;
- } else {
- child = function(){ return parent.apply(this, arguments); };
- }
-
- // Inherit class (static) properties from parent.
- _.extend(child, parent);
-
- // Set the prototype chain to inherit from `parent`, without calling
- // `parent`'s constructor function.
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
-
- // Add prototype properties (instance properties) to the subclass,
- // if supplied.
- if (protoProps) _.extend(child.prototype, protoProps);
-
- // Add static properties to the constructor function, if supplied.
- if (staticProps) _.extend(child, staticProps);
-
- // Correctly set child's `prototype.constructor`.
- child.prototype.constructor = child;
-
- // Set a convenience property in case the parent's prototype is needed later.
- child.__super__ = parent.prototype;
-
- return child;
- };
-
- // Helper function to get a URL from a Model or Collection as a property
- // or as a function.
- var getUrl = function(object) {
- if (!(object && object.url)) return null;
- return _.isFunction(object.url) ? object.url() : object.url;
- };
-
- // Throw an error when a URL is needed, and none is supplied.
- var urlError = function() {
- throw new Error('A "url" property or function must be specified');
- };
-
- // Wrap an optional error callback with a fallback error event.
- var wrapError = function(onError, model, options) {
- return function(resp) {
- if (onError) {
- onError(model, resp, options);
- } else {
- model.trigger('error', model, resp, options);
- }
- };
- };
-
- // Helper function to escape a string for HTML rendering.
- var escapeHTML = function(string) {
- return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
- };
-
-}).call(this);
diff --git a/overviewer_core/data/web_assets/control-bg-active.png b/overviewer_core/data/web_assets/control-bg-active.png
deleted file mode 100644
index ea700f5..0000000
Binary files a/overviewer_core/data/web_assets/control-bg-active.png and /dev/null differ
diff --git a/overviewer_core/data/web_assets/control-bg.png b/overviewer_core/data/web_assets/control-bg.png
deleted file mode 100644
index 595c991..0000000
Binary files a/overviewer_core/data/web_assets/control-bg.png and /dev/null differ
diff --git a/overviewer_core/data/web_assets/icons/marker_home.png b/overviewer_core/data/web_assets/icons/marker_home.png
new file mode 100644
index 0000000..a3d63bc
Binary files /dev/null and b/overviewer_core/data/web_assets/icons/marker_home.png differ
diff --git a/overviewer_core/data/web_assets/icons/marker_home_2x.png b/overviewer_core/data/web_assets/icons/marker_home_2x.png
new file mode 100644
index 0000000..96b1bba
Binary files /dev/null and b/overviewer_core/data/web_assets/icons/marker_home_2x.png differ
diff --git a/overviewer_core/data/web_assets/icons/marker_location.png b/overviewer_core/data/web_assets/icons/marker_location.png
new file mode 100644
index 0000000..d598ad1
Binary files /dev/null and b/overviewer_core/data/web_assets/icons/marker_location.png differ
diff --git a/overviewer_core/data/web_assets/icons/marker_location_2x.png b/overviewer_core/data/web_assets/icons/marker_location_2x.png
new file mode 100644
index 0000000..076c6fa
Binary files /dev/null and b/overviewer_core/data/web_assets/icons/marker_location_2x.png differ
diff --git a/overviewer_core/data/web_assets/images/layers-2x.png b/overviewer_core/data/web_assets/images/layers-2x.png
new file mode 100644
index 0000000..200c333
Binary files /dev/null and b/overviewer_core/data/web_assets/images/layers-2x.png differ
diff --git a/overviewer_core/data/web_assets/images/layers.png b/overviewer_core/data/web_assets/images/layers.png
new file mode 100644
index 0000000..1a72e57
Binary files /dev/null and b/overviewer_core/data/web_assets/images/layers.png differ
diff --git a/overviewer_core/data/web_assets/images/marker-icon-2x.png b/overviewer_core/data/web_assets/images/marker-icon-2x.png
new file mode 100644
index 0000000..e4abba3
Binary files /dev/null and b/overviewer_core/data/web_assets/images/marker-icon-2x.png differ
diff --git a/overviewer_core/data/web_assets/images/marker-icon.png b/overviewer_core/data/web_assets/images/marker-icon.png
new file mode 100644
index 0000000..950edf2
Binary files /dev/null and b/overviewer_core/data/web_assets/images/marker-icon.png differ
diff --git a/overviewer_core/data/web_assets/images/marker-shadow.png b/overviewer_core/data/web_assets/images/marker-shadow.png
new file mode 100644
index 0000000..9fd2979
Binary files /dev/null and b/overviewer_core/data/web_assets/images/marker-shadow.png differ
diff --git a/overviewer_core/data/web_assets/index.html b/overviewer_core/data/web_assets/index.html
index 16b53d9..c890161 100644
--- a/overviewer_core/data/web_assets/index.html
+++ b/overviewer_core/data/web_assets/index.html
@@ -8,17 +8,16 @@
-
+
-
-
-
-
-
+
+
+
+
diff --git a/overviewer_core/data/web_assets/jquery.min.js b/overviewer_core/data/web_assets/jquery.min.js
new file mode 100644
index 0000000..39763f8
--- /dev/null
+++ b/overviewer_core/data/web_assets/jquery.min.js
@@ -0,0 +1,29 @@
+/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
+/*
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;
+
+return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="