slider moves with arrow clicks: $scope.$broadcast('rzSliderForceRender');

slider skip will continue to final chapter
map pans when character is enabled
close buttons in all headers
towns added to search results
search results are clickable
edited about text
This commit is contained in:
Ryan Carpenter
2016-08-10 17:22:06 +08:00
parent 64c363d041
commit 740d5ccfab
4 changed files with 123 additions and 24 deletions
+5
View File
@@ -64,6 +64,7 @@ html, body, ui-gmap-google-map, .angular-google-map, .angular-google-map-contain
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
}
#side h1 {
@@ -73,6 +74,10 @@ html, body, ui-gmap-google-map, .angular-google-map, .angular-google-map-contain
margin: 2px;
}
#side h1 .glyphicon-remove {
cursor: pointer;
}
#side .list-group {
margin: 10px 2px 2px 2px;
}
+30 -10
View File
@@ -105,8 +105,11 @@
<div id="side" ng-class="{wider:(state=='about')}">
<!-- <p>{{state}}</p> -->
<div id="slider" ng-show="state.substring(0,6)=='slider'">
<h1 ng-show="state=='slider-full'">Spoiler Control</h1>
<div id="slider-readout">
<h1 ng-show="state=='slider-full'">
Spoiler Control
<i class="glyphicon glyphicon-remove pull-right" ng-click="toState('slider')"></i>
</h1>
<div id="slider-readout" title="Click to adjust plot spoilers">
<!-- <span class="glyphicon glyphicon-menu-hamburger" ng-click="toState('menu')"></span> -->
<span ng-show="slider.show=='episodes'" ng-bind="episodes[slider.currentEpisode].name" title="{{episodes[slider.currentEpisode].name}}" ng-click="toState('slider-full')"></span>
<span ng-show="slider.show=='chapters'" ng-bind="chapters[slider.currentChapter].name" title="{{chapters[slider.currentChapter].name}}" ng-click="toState('slider-full')"></span>
@@ -123,11 +126,13 @@
</button>
</div>
<rzslider
id="slide-episodes"
ng-show="slider.show=='episodes'"
rz-slider-model="slider.currentEpisode"
rz-slider-options="{floor:0, ceil:episodes.length-1}">
</rzslider>
<rzslider
id="slide-chapters"
ng-show="slider.show=='chapters'"
rz-slider-model="slider.currentChapter"
rz-slider-options="{floor:0, ceil:chapters.length-1}">
@@ -156,7 +161,10 @@
</div>
<div id="location" ng-show="state=='location'">
<h1>Location Details</h1>
<h1>
Location Details
<i class="glyphicon glyphicon-remove pull-right" ng-click="toState('slider')"></i>
</h1>
<div class="list-group">
<a ng-href="{{locationDetail.url}}" role="button" class="list-group-item btn-clear">
<i class="glyphicon glyphicon glyphicon-map-marker"></i>
@@ -177,10 +185,13 @@
</div>
<div id="characters" ng-show="state=='characters'">
<h1>Characters</h1>
<h1>
Characters
<i class="glyphicon glyphicon-remove pull-right" ng-click="toState('slider')"></i>
</h1>
<ul class="list-group" qm-list="{{characters.length}}">
<li ng-repeat="character in characters" class="list-group-item">
<a ng-href="{{character.house.url}}" ng-if="character.house!=''" class="hasimg">
<a ng-href="{{character.house.url}}" ng-if="character.house!=''" class="hasimg" target="_blank">
<img ng-src="{{character.house.img}}">
</a>
<a ng-href="{{character.url}}" ng-class="{noimg:(character.house=='')}">{{character.name}}</a>
@@ -195,7 +206,10 @@
</div>
<div id="search" ng-show="state=='search'">
<h1>Search</h1>
<h1>
Search
<i class="glyphicon glyphicon-remove pull-right" ng-click="toState('slider')"></i>
</h1>
<div class="input-group">
<input id="searchInput" ng-model="search" type="text" class="form-control" placeholder="towns, houses, characters, titles">
<span class="input-group-addon">
@@ -206,7 +220,7 @@
<button
ng-repeat="result in searchResults | filter:{ title: search }"
class="list-group-item"
ng-click="result.click()">
ng-click="resultClick(result)">
<i class="glyphicon {{result.icon}}"></i>
<span>
{{result.title}}
@@ -218,7 +232,10 @@
</div>
<div id="options" ng-show="state=='options'">
<h1>Options</h1>
<h1>
Options
<i class="glyphicon glyphicon-remove pull-right" ng-click="toState('slider')"></i>
</h1>
<ul class="list-group">
<li class="list-group-item">
Character Paths
@@ -260,11 +277,14 @@
</div>
<div id="about" ng-show="state=='about'" qmList>
<h1>About Quatrermaester.info</h1>
<h1>
About Quatrermaester.info
<i class="glyphicon glyphicon-remove pull-right" ng-click="toState('slider')"></i>
</h1>
<p>This is a map of the world depicted in the novel series <a target="_blank" href="http://www.georgerrmartin.com/bibliography.html">A Song of Ice & Fire</a> and the TV adaptation, <a target="_blank" href="http://www.hbo.com/game-of-thrones">A Game of Thrones</a>. The website is designed to display useful information on top of the map layer and link to additional details on the <a target="_blank" href="http://awoiaf.westeros.org">Westeros.org wiki site</a>.</p>
<p>If you haven't finished reading the novels or watching the shows, you can hide any details that might spoil the plot. Simply drag the slider in the top-right corner to the last chapter or episode that you've completed. You can move the slider with arrow buttons in order to animate the characters' paths. Keep in mind that the slider only controls this map; any page you may open on <span style="font-style:italic;">the wiki site may contain spoilers</span>.</p>
<!-- <p>If you'd like to share this map with others and point them to a specific location, hold down the Ctrl button while you click on the map. You will be given a URL that will automatically center the map on that point. (e.g. <a id="lightboxLink" href="#@Winterfell">Winterfell</a>)</p> -->
<p>The map layer used in this website was drawn by <a target="_blank" href="http://www.sermountaingoat.co.uk/map">theMountainGoat</a>, based on previous work by <a target="_blank" href="http://www.cartographersguild.com/finished-maps/6683-westeros-fan-project.html">Tear of the Cartographer's Guild</a>. The map interface is based on the <a target="_blank" href="http://developers.google.com/maps/documentation/javascript/">Google Maps API</a>, with custom tiles generated by <a target="_blank" href="http://www.bartlett.ucl.ac.uk/casa/latest/software/gmap-image-cutter">GMap Image Cutter Version 1.42</a> and arrows using code from <a target="_blank" href="http://www.bdcc.co.uk/Gmaps/Services.htm">Bill Chadwick</a>. Intellectual property of <span style="font-style:italic;">A Song of Ice and Fire</span>, this map and all locations belong to <a target="_blank" href="http://www.georgerrmartin.com">George R.R. Martin</a>. If you find any errors in the map data, please <a target="_blank" href="http://asoiaf.westeros.org/index.php/user/55796-carpiediem/">alert me on the Westeros.org message board</a>.</p>
<p>The map layer used in this website was drawn by <a target="_blank" href="http://www.sermountaingoat.co.uk/map">theMountainGoat</a>, based on previous work by <a target="_blank" href="http://www.cartographersguild.com/finished-maps/6683-westeros-fan-project.html">Tear of the Cartographer's Guild</a>. The map interface is built on the <a target="_blank" href="http://developers.google.com/maps/documentation/javascript/">Google Maps API</a>, through the <a target="_blank" href="http://angular-ui.github.io/angular-google-maps/">angular-google-maps directive</a>. It displays custom tiles generated by <a target="_blank" href="http://www.bartlett.ucl.ac.uk/casa/latest/software/gmap-image-cutter">GMap Image Cutter Version 1.42</a>. Intellectual property of <span style="font-style:italic;">A Song of Ice and Fire</span>, this map and all locations belong to <a target="_blank" href="http://www.georgerrmartin.com">George R.R. Martin</a>. If you find any errors in the map data, please <a target="_blank" href="mailto:ryan@rslc.us">let me know</a>.</p>
<p>You may notice that this map is contradicted, in places, by the maps shown in <a target="_blank" href="http://www.randomhouse.com/book/221316/the-lands-of-ice-and-fire-a-game-of-thrones-by-george-rr-martin/other">The Lands of Ice and Fire</a>. Since the maps in that collection are clearly copyrighted material, I have no intention of copying them directly and I'm going to remain cautious in pulling details from them to correct this website. You can find low-resolution versions of the official maps on <a target="_blank" href="http://www.fantasticmaps.com/the-lands-of-ice-and-fire/">the artist's website</a> or in Random House's <a target="_blank" href="https://itunes.apple.com/hk/app/george-r.-r.-martins-world/id570015223">iOS</a> & <a target="_blank" href="https://play.google.com/store/apps/details?id=com.randomhouse.woiaf">Android</a> apps.</p>
<h1>Other Resources</h1>
+80 -5
View File
@@ -52,6 +52,7 @@ angular.module('quartermaester')
$scope.toState = toState;
$scope.slideTo = slideTo;
$scope.panTo = panTo;
$scope.resultClick = resultClick;
$scope.refreshMap = refreshMap;
$scope.locationDetail = null;
@@ -74,6 +75,7 @@ angular.module('quartermaester')
$scope.$watch('slider', refreshMap, true);
$scope.$watch('options', refreshMap, true);
$scope.$watch('options.characters', panToCharacter, true);
for (var i=0;i<qmData.characters.length; i++) {
$scope.options.characters[qmData.characters[i].key] = false;
@@ -89,16 +91,30 @@ angular.module('quartermaester')
$scope.mapModels.characters = $filter('qmSlider')(qmData.characterMarkers, $scope.slider, $scope.options);
$scope.mapModels.paths = $filter('qmSlider')(qmData.characterPaths, $scope.slider, $scope.options);
//console.log("mapModels.paths", $scope.mapModels.paths);
}
function panToCharacter(newValue, oldValue) {
for (var character in newValue) {
if (newValue[character] && !oldValue[character]) {
var characterMarkers = $filter('filter')($scope.mapModels.characters, {character: {key: character}});
if (characterMarkers.length==0) return false;
var coords = characterMarkers[characterMarkers.length-1].coords
$scope.map.control.getGMap().panTo({lat: coords.latitude, lng: coords.longitude});
return true;
}
}
}
function toState(stateName) {
$scope.state = stateName;
if (stateName=="search")
$timeout(function() {
document.getElementById("searchInput").focus();
});
if (stateName=="search") $timeout(function() {
document.getElementById("searchInput").focus();
});
// if (stateName=="slider-full") $timeout(function() {
// console.log(document.getElementById("slide-"+$scope.slider.show).getElementsByClassName("rz-pointer"));
// document.getElementById("slide-"+$scope.slider.show).getElementsByClassName("rz-pointer")[0].focus();
// });
}
function slideTo(input) {
@@ -128,6 +144,10 @@ angular.module('quartermaester')
if ($scope.slider[measure]==sliderMax) break;
if ($scope.slider.show=="episodes") $scope.slider[measure] = Math.min(sliderMax, Math.ceil(($scope.slider[measure]+2)/10)*10-1);
else {
if ($scope.slider[measure] >= 270) {
$scope.slider[measure] = 344;
break;
}
for (var bookId=0; bookId<=4; bookId++) {
var epilogue = parseInt(qmData.books[bookId].precedingChapters, 10)-1;
if (epilogue < $scope.slider[measure]+1) continue;
@@ -153,7 +173,7 @@ angular.module('quartermaester')
}
break;
}
$scope.$broadcast('rzSliderForceRender');
}
function panTo(input) {
@@ -165,6 +185,61 @@ angular.module('quartermaester')
$scope.state = "location";
}
function resultClick(model) {
switch (model.icon) {
case "glyphicon-map-marker":
var town = $filter('filter')($scope.mapModels.towns, {key: model.key})[0];
$scope.map.control.getGMap().panTo({lat: town.coords.latitude, lng: town.coords.longitude});
$scope.locationDetail = town;
$scope.state = "location";
break;
case "glyphicon-user":
$scope.options.characters[model.key] = true;
var character = $filter('filter')($scope.characters, {key: model.key})[0];
var allCharacterMarkers = $filter('qmSlider')(qmData.characterMarkers, $scope.slider, $scope.options);
var matchedCharacterMarkers = $filter('filter')(allCharacterMarkers, {character: {key: model.key}});
$scope.locationDetail = {
name: "",
url: "",
house: character.name,
houseImg: character.house.img,
houseUrl: character.url,
direct: "#"
};
$scope.state = "location";
if (matchedCharacterMarkers.length==0) break;
var thisCharacterMarker = matchedCharacterMarkers[0];
$scope.map.control.getGMap().panTo({lat: thisCharacterMarker.coords.latitude, lng: thisCharacterMarker.coords.longitude});
$scope.locationDetail.name = thisCharacterMarker.town.name;
$scope.locationDetail.url = thisCharacterMarker.town.url;
$scope.locationDetail.direct = ($scope.slider.show=="episodes") ? thisCharacterMarker.episode.url : thisCharacterMarker.chapter.url;
break;
case "glyphicon-home":
var house = $filter('filter')(qmData.heraldry, {seat: model.subtitle})[0];
$scope.map.control.getGMap().panTo({lat: house.coords.latitude, lng: house.coords.longitude});
$scope.locationDetail = {
name: house.seat,
url: house.seatUrl,
house: house.name,
houseImg: house.houseImg,
houseUrl: house.url,
direct: house.direct
};
$scope.state = "location";
break;
case "glyphicon-facetime-video":
case "glyphicon-book":
slideTo(model.abbr);
break;
default:
console.log("resultClick", model);
}
}
function locationClick(marker, eventName, model) {
$scope.locationDetail = model;
$scope.state = "location";
+8 -9
View File
@@ -138,7 +138,7 @@ angular.module('quartermaester')
icon: "glyphicon-facetime-video",
title: episode.title,
subtitle: "HBO episode",
abbr: "S" + episode.season + "E" + epNumber + ": " + episode.title,
abbr: "S" + episode.season + "E" + epNumber,
click: function(){slideTo(this.abbr)}
};
}
@@ -213,6 +213,7 @@ angular.module('quartermaester')
longitude: parseFloat(town.longitude)
},
options: {
// zindex: 2, // bug prevents this form working
icon: {path: 0, strokeOpacity: 0, scale: 3}
}
});
@@ -228,9 +229,8 @@ angular.module('quartermaester')
return {
icon: "glyphicon-map-marker",
key: town.key,
title: town.name,
subtitle: town.region.replace(/_/g," "),
click: function(){panTo(this.key)}
title: decodeURI(town.key).replace(/_/g," "),
subtitle: town.region.replace(/_/g," ")
};
}
@@ -255,8 +255,7 @@ angular.module('quartermaester')
icon: "glyphicon-user",
key: character.key,
title: character.name,
subtitle: "", //decodeURI(character.house).replace(/_/g," "),
click: function(){showCharacter(this.key)}
subtitle: "", //decodeURI(character.house).replace(/_/g," ")
};
}
@@ -292,6 +291,7 @@ angular.module('quartermaester')
direct: "#@" + house.seatKey,
coords: coords,
options: {
// zindex: 1, // bug prevents this form working
icon: {
url: house.img,
scaledSize: house.isGreat ? {height:60, width:50} : {height:30, width:25},
@@ -305,8 +305,7 @@ angular.module('quartermaester')
return {
icon: "glyphicon-home",
title: decodeURI(house.wikiKey).replace(/_/g," "),
subtitle: (house.seatKey=="") ? house.clue : decodeURI(house.seatKey).replace(/_/g," "),
click: function(){showCharacter(character.key)}
subtitle: (house.seatKey=="") ? house.clue : decodeURI(house.seatKey).replace(/_/g," ")
};
}
@@ -486,7 +485,7 @@ angular.module('quartermaester')
// var listHeight = attrs.qmList ? 41*parseInt(attrs.qmList, 10) : 0;
// console.log(winHeight, attrs.qmList, listHeight);
// if (listHeight>winHeight) elem.css('height', winHeight - 48 + 'px');
elem.css('height', winHeight - 50 + 'px');
elem.css('height', winHeight - 55 + 'px');
}
};
});