Skip to content

Instantly share code, notes, and snippets.

@PeterBooker
Last active July 8, 2021 13:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PeterBooker/1bbb36780672920c06900dff135a2162 to your computer and use it in GitHub Desktop.
Save PeterBooker/1bbb36780672920c06900dff135a2162 to your computer and use it in GitHub Desktop.
Dota 2 Tooltips
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__(1);
module.exports = __webpack_require__(3);
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var urls_1 = __webpack_require__(2);
var mTTips;
(function (mTTips) {
var Store = {};
function Init() {
console.log("Starting Tooltip Init");
var cssId = "mttip-css";
if (!document.getElementById(cssId)) {
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.id = cssId;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'dist/mttip.css';
link.media = 'all';
head.appendChild(link);
}
window.onload = function () {
var links = document.links;
var regex = /mtstats/gi;
for (var i = 0; i < links.length; i++) {
var a = links[i];
var href = a.attributes["href"].value;
if (href.search(regex) != -1) {
a.addEventListener("mouseenter", mouseIn);
a.addEventListener("mousemove", mouseMove);
a.addEventListener("mouseleave", mouseOut);
}
}
};
console.log("Completed Tooltip Init");
}
mTTips.Init = Init;
function mouseIn(event) {
console.log(window.innerHeight);
console.log(window.outerHeight);
var el = document.getElementById("mttip");
if (!el) {
el = document.createElement("div");
el.id = "mttip";
el.style.display = "none";
el.style.position = "absolute";
document.body.appendChild(el);
}
el.style.display = "block";
var targetElem = event.target;
var href = targetElem.attributes["href"].value;
var item = getEndOfURL(href);
var data;
if (!(item in Store)) {
el.innerText = "Loading...";
getCORS(urls_1.mtStatsURLs.heroAPIURL + item + "/", function (request) {
var response = request.currentTarget.response || request.target.responseText;
var data = JSON.parse(response);
Store[data.Name] = data;
var Tip = new HeroTooltip(data);
el = Tip.buildTip(el);
});
}
else {
data = Store[item];
var Tip = new HeroTooltip(data);
el = Tip.buildTip(el);
}
var posX = event.pageX;
var posY = event.pageY;
var rect = el.getBoundingClientRect();
console.log(el.getBoundingClientRect());
var linkLeftOffset = rect.left;
var linkTopOffset = rect.top;
var linkWidth = rect.width;
var linkHeight = rect.height;
var tipWidth = el.clientWidth;
var tipHeight = el.clientHeight;
var left = linkTopOffset - window.scrollY;
var top = linkLeftOffset + linkHeight - window.scrollX;
el.style.left = left + "px";
el.style.top = top + "px";
}
function mouseMove(event) {
}
function mouseOut(event) {
var el = document.getElementById("mttip");
if (el) {
el.style.display = "none";
el.innerHTML = "";
el.innerText = "";
}
console.log(Store);
}
var Tooltip = (function () {
function Tooltip() {
}
return Tooltip;
}());
var HeroTooltip = (function (_super) {
__extends(HeroTooltip, _super);
function HeroTooltip(heroData) {
var _this = _super.call(this) || this;
_this.data = heroData;
return _this;
}
HeroTooltip.prototype.getClass = function () {
return "hero-" + this.data.Name;
};
HeroTooltip.prototype.getImage = function (size) {
var url;
var name = this.getNameFromAlias(this.data.Name);
switch (size) {
case "full": {
url = urls_1.ImageURLs.heroImageURL + name + "_full.png";
break;
}
case "large": {
url = urls_1.ImageURLs.heroImageURL + name + "_lg.png";
break;
}
case "small": {
url = urls_1.ImageURLs.heroImageURL + name + "_sb.png";
break;
}
case "vertical": {
url = urls_1.ImageURLs.heroImageURL + name + "_vert.jpg";
break;
}
default: {
url = urls_1.ImageURLs.heroImageURL + name + "_lg.png";
break;
}
}
return url;
};
HeroTooltip.prototype.getIconClasses = function (stat) {
if (this.data.PrimaryStat === stat) {
return "primary icon";
}
else {
return "icon";
}
};
HeroTooltip.prototype.getNameFromAlias = function (name) {
switch (name) {
case "clockwerk":
name = "rattletrap";
break;
case "shadow_fiend":
name = "nevermore";
break;
case "outworld_devourer":
name = "obsidian_destroyer";
break;
case "timbersaw":
name = "shredder";
break;
case "queen_of_pain":
name = "queenofpain";
break;
case "io":
name = "wisp";
break;
case "vengeful_spirit":
name = "vengefulspirit";
break;
case "zeus":
name = "zuus";
break;
case "underlord":
name = "abyssal_underlord";
break;
case "necrophos":
name = "necrolyte";
break;
case "wraith_king":
name = "skeleton_king";
break;
}
return name;
};
HeroTooltip.prototype.buildTip = function (el) {
var tipContent = "\n <div class=\"hdr\">\n <span class=\"portrait\">\n <img class=\"img\" src=\"" + this.getImage('vertical') + "\" alt=\"" + this.data.Localized.Name + " Portrait - Dota 2 Hero\" />\n </span>\n <h2 class=\"title\">" + this.data.Localized.Name + "</h2>\n <div class=\"role\">\n <span class=\"atk\">" + this.data.Localized.Atk + "</span>\n <span class=\"roles\">" + this.data.Localized.Roles.join(' - ') + "</span>\n </div>\n <div class=\"stats\">\n <span class=\"stat-grp\">\n <img class=\"" + this.getIconClasses('int') + "\" src=\"" + urls_1.ImageURLs.intIconURL + "\" alt=\"Intelligence Icon\" />\n <span class=\"stat\">" + this.data.IntBase + "</span>\n </span>\n <span class=\"stat-grp\">\n <img class=\"" + this.getIconClasses('agi') + "\" src=\"" + urls_1.ImageURLs.agiIconURL + "\" alt=\"Agility Icon\" />\n <span class=\"stat\">" + this.data.AgiBase + "</span>\n </span>\n <span class=\"stat-grp\">\n <img class=\"" + this.getIconClasses('str') + "\" src=\"" + urls_1.ImageURLs.strIconURL + "\" alt=\"Strength Icon\" />\n <span class=\"stat\">" + this.data.StrBase + "</span>\n </span>\n </div>\n <div class=\"stats\">\n <span class=\"stat-grp\">\n <img class=\"icon atk\" src=\"" + urls_1.ImageURLs.attackIconURL + "\" alt=\"Attack Icon\" />\n <span class=\"stat\">" + (this.data.AttackMin + '-' + this.data.AttackMax) + "</span>\n </span>\n <span class=\"stat-grp\">\n <img class=\"icon ms\" src=\"" + urls_1.ImageURLs.speedIconURL + "\" alt=\"Move Speed Icon\" />\n <span class=\"stat\">" + this.data.MoveSpeed + "</span>\n </span>\n <span class=\"stat-grp\">\n <img class=\"icon arm\" src=\"" + urls_1.ImageURLs.armorIconURL + "\" alt=\"Armor Icon\" />\n <span class=\"stat\">" + this.data.Armor + "</span>\n </span>\n </div>\n </div>\n <div class=\"ftr\">\n <div class=\"bio\">" + this.data.Localized.Bio + "</div>\n </div>\n ";
el.innerHTML = tipContent;
return el;
};
return HeroTooltip;
}(Tooltip));
var AbilityTooltip = (function () {
function AbilityTooltip(abilityData) {
this.data = abilityData;
}
AbilityTooltip.prototype.getClass = function () {
return "ability-" + this.data.Name;
};
return AbilityTooltip;
}());
var ItemTooltip = (function () {
function ItemTooltip(itemData) {
this.data = itemData;
}
ItemTooltip.prototype.getClass = function () {
return "item-" + this.data.Name;
};
return ItemTooltip;
}());
function getEndOfURL(url) {
url = url.replace(/#[^#]+$/, "").replace(/\?[^\?]+$/, "").replace(/\/$/, "");
console.log(url);
return url.substr(url.lastIndexOf("/") + 1);
}
function getItemType(url) {
switch (getEndOfURL(url)) {
case "hero": {
break;
}
case "ability": {
break;
}
case "item": {
break;
}
}
}
function getCORS(url, success) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = success;
xhr.send();
return xhr;
}
})(mTTips || (mTTips = {}));
mTTips.Init();
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ImageURLs;
(function (ImageURLs) {
ImageURLs.heroImageURL = "http://cdn.dota2.com/apps/dota2/images/heroes/";
ImageURLs.agiIconURL = "http://cdn.dota2.com/apps/dota2/images/heropedia/overviewicon_agi.png";
ImageURLs.strIconURL = "http://cdn.dota2.com/apps/dota2/images/heropedia/overviewicon_str.png";
ImageURLs.intIconURL = "http://cdn.dota2.com/apps/dota2/images/heropedia/overviewicon_int.png";
ImageURLs.attackIconURL = "http://cdn.dota2.com/apps/dota2/images/heropedia/overviewicon_attack.png";
ImageURLs.speedIconURL = "http://cdn.dota2.com/apps/dota2/images/heropedia/overviewicon_speed.png";
ImageURLs.armorIconURL = "http://cdn.dota2.com/apps/dota2/images/heropedia/overviewicon_defense.png";
})(ImageURLs = exports.ImageURLs || (exports.ImageURLs = {}));
var mtStatsURLs;
(function (mtStatsURLs) {
mtStatsURLs.heroAPIURL = "http://tooltips.mtstats.com/api/v1/hero/";
mtStatsURLs.abilityAPIURL = "http://tooltips.mtstats.com/api/v1/ability/";
mtStatsURLs.itemAPIURL = "http://tooltips.mtstats.com/api/v1/item/";
})(mtStatsURLs = exports.mtStatsURLs || (exports.mtStatsURLs = {}));
/***/ }),
/* 3 */
/***/ (function(module, exports) {
// removed by extract-text-webpack-plugin
/***/ })
/******/ ]);
import { DataTypes } from "./types/tooltips"
import { ImageURLs, mtStatsURLs } from "./constants/urls"
/**
* mTTips contains all the Tooltips functionality.
*/
module mTTips {
/**
* StoreData describes the types allowed in the Store.
*/
interface StoreData {
[key: string]: DataTypes.Hero | DataTypes.Ability | DataTypes.Item
}
/**
* Store keeps a track of all API response data
* This ensures that we do not need to request data multiple times.
*/
let Store: StoreData = {}
export function Init() {
console.log("Starting Tooltip Init")
let cssId: string = "mttip-css"
if ( !document.getElementById(cssId) ) {
let head: HTMLHeadElement = document.getElementsByTagName('head')[0]
let link: HTMLLinkElement = document.createElement('link')
link.id = cssId;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'dist/mttip.css';
link.media = 'all';
head.appendChild(link);
}
window.onload = () => {
let links = document.links
let regex = /mtstats/gi
for ( let i = 0; i < links.length; i++ ) {
let a = links[i]
let href: string = a.attributes["href"].value
if ( href.search(regex) != -1 ) {
// FIX: Is this useful?
//a.className = "mt-tip"
a.addEventListener("mouseenter", mouseIn)
a.addEventListener("mousemove", mouseMove)
a.addEventListener("mouseleave", mouseOut)
}
}
}
console.log("Completed Tooltip Init")
}
/**
* mouseIn() runs when the mouse enters a relevant link, if the #mttip element does not exist it is created.
* The anchor URL is parsed to identify the type of Tooltip and an API request made for the data, this is
* then used to build the tooltip content.
*
* @param {Event} event
*/
function mouseIn(event: MouseEvent) {
let el = document.getElementById("mttip")
if ( !el ) {
el = document.createElement("div")
el.id = "mttip"
el.style.display = "none"
el.style.position = "absolute"
document.body.appendChild(el)
}
el.style.display = "block"
let targetElem = <HTMLAnchorElement>event.target
let href: string = targetElem.attributes["href"].value
let item: string = getEndOfURL(href)
let data: DataTypes.Hero
// If not already stored, fetch data
if ( ! ( item in Store ) ) {
el.innerText = "Loading..."
getCORS(mtStatsURLs.heroAPIURL + item + "/", function(request) {
let response: string = request.currentTarget.response || request.target.responseText
let data: DataTypes.Hero = JSON.parse(response)
Store[data.Name] = data
let Tip: HeroTooltip = new HeroTooltip(data)
el = Tip.buildTip(el)
});
} else {
data = <DataTypes.Hero>Store[item]
let Tip: HeroTooltip = new HeroTooltip(data)
el = Tip.buildTip(el)
}
let posX: number = event.pageX
let posY: number = event.pageY
let rect: ClientRect = el.getBoundingClientRect()
let link: EventTarget = event.target
let linkLeftOffset: number = rect.left
let linkTopOffset: number = rect.top
let linkWidth: number = rect.width
let linkHeight: number = rect.height
let tipWidth: number = el.clientWidth
let tipHeight: number = el.clientHeight
let left: number = linkTopOffset - window.scrollY
let top: number = linkLeftOffset + linkHeight - window.scrollX
el.style.left = left + "px"
el.style.top = top + "px"
}
/**
* mouseMove() runs when the mouse moves over a relevant link.
* It constantly positons the Tooltip relative to the mouse.
* TODO: Make the Tooltip fixed position central to the link,
* this would allow the user to select and copy tooltip data.
*
* @param {Event} event
*/
function mouseMove(event: MouseEvent) {
/*let el = document.getElementById("mttip")
let leftOffset: number = 0
let topOffset: number = 18
let left: string = event.pageX + "px"
let top: string = event.pageY + "px"
if ( el ) {
el.style.top = top
el.style.left = left
}*/
}
/**
* mouseOut() runs when the mouse leaves a relevant link.
* If the #mttip element exists, it is emptied and made invisible.
*
* @param {Event} event
*/
function mouseOut(event: MouseEvent) {
let el = document.getElementById("mttip")
if ( el ) {
el.style.display = "none"
el.innerHTML = ""
el.innerText = ""
}
console.log(Store)
}
/**
* Tooltip contains the shared structure of all Tooltips.
* TODO: Store reference to #mttip element in all Tooltip classes.
*/
class Tooltip {
element: HTMLElement
constructor() {
}
}
/**
* HeroTooltip
*/
class HeroTooltip extends Tooltip {
// Contains all Hero data
data: DataTypes.Hero
/**
* constructor()
*
* @param {DataTypes.Hero} heroData
*/
constructor( heroData: DataTypes.Hero ) {
super()
this.data = heroData
}
/**
* getClass() returns a CSS class string
* based on internal class data
*
* @return {String}
*/
getClass() {
return "hero-" + this.data.Name
}
/**
* getImage() uses the size parameter and class data to build a full image url.
*
* @param {String} size
* @return {String} url
*/
getImage(size: string) {
let url: string
/**
* getNameFromAlias() converts Hero Aliases to Names
* e.g. 'shadow_fiend' to 'nevermore'
*/
let name: string = this.getNameFromAlias(this.data.Name)
// Build URL based on size parameter
switch(size) {
case "full": {
url = ImageURLs.heroImageURL + name + "_full.png"
break;
}
case "large": {
url = ImageURLs.heroImageURL + name + "_lg.png"
break;
}
case "small": {
url = ImageURLs.heroImageURL + name + "_sb.png"
break;
}
case "vertical": {
url = ImageURLs.heroImageURL + name + "_vert.jpg"
break;
}
default: {
url = ImageURLs.heroImageURL + name + "_lg.png"
break;
}
}
return url
}
/**
* getIconClasses() returns the CSS class string for hero utility icons
*
* @param {String} stat
* @return {String}
*/
getIconClasses(stat: string) {
if ( this.data.PrimaryStat === stat ) {
return "primary icon"
} else {
return "icon"
}
}
/**
* getNameFromAlias() returns the Hero Name based on the input name
* If name contains an Alias it is converted to the relevant Name
*
* @param {String} name
* @return {String}
*/
getNameFromAlias(name: string) {
switch (name) {
case "clockwerk":
name = "rattletrap"
break
case "shadow_fiend":
name = "nevermore"
break
case "outworld_devourer":
name = "obsidian_destroyer"
break
case "timbersaw":
name = "shredder"
break
case "queen_of_pain":
name = "queenofpain"
break
case "io":
name = "wisp"
break
case "vengeful_spirit":
name = "vengefulspirit"
break
case "zeus":
name = "zuus"
break
case "underlord":
name = "abyssal_underlord"
break
case "necrophos":
name = "necrolyte"
break
case "wraith_king":
name = "skeleton_king"
break
}
return name
}
/**
* buildTip() adds the relevant HTML and content to the element passed in.
*
* @param {HTMLElement} el
* @return {HTMLElement}
*/
buildTip(el: HTMLElement) {
let tipContent: string = `
<div class="hdr">
<span class="portrait">
<img class="img" src="${this.getImage('vertical')}" alt="${this.data.Localized.Name} Portrait - Dota 2 Hero" />
</span>
<h2 class="title">${this.data.Localized.Name}</h2>
<div class="role">
<span class="atk">${this.data.Localized.Atk}</span>
<span class="roles">${this.data.Localized.Roles.join(' - ')}</span>
</div>
<div class="stats">
<span class="stat-grp">
<img class="${this.getIconClasses('int')}" src="${ImageURLs.intIconURL}" alt="Intelligence Icon" />
<span class="stat">${this.data.IntBase}</span>
</span>
<span class="stat-grp">
<img class="${this.getIconClasses('agi')}" src="${ImageURLs.agiIconURL}" alt="Agility Icon" />
<span class="stat">${this.data.AgiBase}</span>
</span>
<span class="stat-grp">
<img class="${this.getIconClasses('str')}" src="${ImageURLs.strIconURL}" alt="Strength Icon" />
<span class="stat">${this.data.StrBase}</span>
</span>
</div>
<div class="stats">
<span class="stat-grp">
<img class="icon atk" src="${ImageURLs.attackIconURL}" alt="Attack Icon" />
<span class="stat">${this.data.AttackMin + '-' + this.data.AttackMax}</span>
</span>
<span class="stat-grp">
<img class="icon ms" src="${ImageURLs.speedIconURL}" alt="Move Speed Icon" />
<span class="stat">${this.data.MoveSpeed}</span>
</span>
<span class="stat-grp">
<img class="icon arm" src="${ImageURLs.armorIconURL}" alt="Armor Icon" />
<span class="stat">${this.data.Armor}</span>
</span>
</div>
</div>
<div class="ftr">
<div class="bio">${this.data.Localized.Bio}</div>
</div>
`
el.innerHTML = tipContent
return el
}
}
/**
* AbilityTooltip
*/
class AbilityTooltip {
data: DataTypes.Ability
constructor( abilityData: DataTypes.Ability ) {
this.data = abilityData
}
getClass() {
return "ability-" + this.data.Name
}
}
/**
* ItemTooltip
*/
class ItemTooltip {
data: DataTypes.Item
constructor( itemData: DataTypes.Item ) {
this.data = itemData
}
getClass() {
return "item-" + this.data.Name
}
}
// Get the Last Section of URL
// Taken from https://stackoverflow.com/a/10290657
function getEndOfURL(url: string) {
url = url.replace(/#[^#]+$/, "").replace(/\?[^\?]+$/, "").replace(/\/$/, "")
console.log(url)
return url.substr(url.lastIndexOf("/") + 1)
}
function getItemType(url: string) {
switch(getEndOfURL(url)) {
case "hero": {
//statements;
break;
}
case "ability": {
//statements;
break;
}
case "item": {
//statements;
break;
}
}
}
// HTTP GET Request
function getCORS(url, success) {
var xhr = new XMLHttpRequest()
xhr.open("GET", url)
xhr.onload = success
xhr.send()
return xhr
}
}
mTTips.Init()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment