NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Nikki's Info Add By Number // @namespace holatuwol // @version 5.8 // @license 0BSD // @downloadURL https://openuserjs.org/install/holatuwol/Nikkis_Info_Add_By_Number.user.js // @updateURL https://openuserjs.org/meta/holatuwol/Nikkis_Info_Add_By_Number.meta.js // @match https://ln.nikkis.info/* // @grant unsafeWindow // @require https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js // ==/UserScript== /** * Compiled from TypeScript * https://github.com/holatuwol/myni-userscript */ var styleElement = document.createElement('style'); styleElement.textContent = "\n.have-witem.safe-decompose {\n background-color: #efe !important;\n}\n\n.secondary-content div {\n text-align: right;\n}\n\n.insert-by-number {\n margin-top: 2em;\n}\n\n.transitive-dependencies,\n.crafting-path > div {\n display: flex;\n flex-direction: row;\n align-items: center;\n}\n\n.transitive-dependencies {\n flex-wrap: wrap;\n max-height: 50vh;\n overflow-y: auto;\n}\n\n.crafting-path {\n display: flex;\n flex-direction: column;\n max-height: 20vh;\n overflow: auto;\n}\n\n.crafting-path .needed {\n text-align: center;\n}\n\n.crafting-path .needed .arrow {\n font-size: 3em;\n line-height: 0.2em;\n}\n\n.transitive-dependencies .icon,\n.crafting-path .icon {\n position: relative;\n}\n\n.transitive-dependencies .icon-room,\n.crafting-path .icon-room {\n padding: 5px 26px 15px 5px;\n}\n\n.transitive-dependencies.show-path .witem {\n opacity: 0.2;\n}\n\n.transitive-dependencies.show-path .witem.active {\n opacity: 1.0;\n}\n\n.attr-metadata,\n.drop-metadata,\n.suit-metadata {\n font-size: 0.8rem;\n line-height: 1rem;\n}\n\n.stage-progress {\n display: flex;\n justify-content: flex-end;\n margin-left: auto;\n}\n\n.stage-progress input[type=text] {\n height: 2rem;\n width: 5rem;\n text-align: right;\n}\n\n.sitem .di img,\n.witem .di img,\n.drop-metadata img {\n vertical-align: text-bottom;\n}\n\nimg.suit-dia.store:not(.have-suit-part) {\n border: solid 1px;\n}\n\n.base-score .new-icon {\n position: relative;\n padding: 0.2em 1em 0.2em 1em;\n width: auto;\n}\n"; var head = document.querySelector('head'); head.appendChild(styleElement); var pathParts = document.location.pathname.split('/'); var checkSessionURL = 'https://my.nikkis.info/checksession'; var getWardrobeURL = 'https://my.nikkis.info/getwardrobe/ln'; var updateWardrobeURL = 'https://my.nikkis.info/updatewardrobe/ln'; /** * Creates a button. */ function createButton(text, callback) { var button = document.createElement('a'); button.classList.add('waves-effect'); button.classList.add('waves-light'); button.classList.add('btn'); button.classList.add('pink'); button.classList.add('lighten-2'); button.addEventListener('click', callback); button.textContent = text; return button; } /** * Function to retrieve the section with the specified header. */ function getSection(container, headerName, referenceElement) { if (referenceElement === void 0) { referenceElement = null; } var headers = Array.from(container.querySelectorAll('h5')); for (var i = 0; i < headers.length; i++) { var content = (headers[i].textContent || '').trim(); if (content == headerName) { return headers[i].parentElement; } } if (!referenceElement) { return null; } var divider = document.createElement('div'); divider.classList.add('divider'); var section = document.createElement('div'); section.classList.add('section'); var header = document.createElement('h5'); header.classList.add('item-section-head'); header.textContent = headerName; section.appendChild(header); var referenceElementParentElement = referenceElement.parentElement; referenceElementParentElement.insertBefore(divider, referenceElement); referenceElementParentElement.insertBefore(section, referenceElement); return section; } /** * Retrieves the current wardrobe, and then invokes the provided * callback function, passing the current wardrobe. */ function getWardrobe(callback) { jQuery.getJSON(checkSessionURL, function (d1) { if (!d1['authenticated']) { return; } jQuery.getJSON(getWardrobeURL, function (d2) { callback(d2.wardrobe); }); }); } /** * Make an XMLHttpRequest, and then invoke the specified callback once you have * constructed a document from it. */ function processXMLHttpRequest(href, callback) { var xhr = new XMLHttpRequest(); xhr.open('GET', href); // https://stackoverflow.com/questions/20583396/queryselectorall-to-html-from-another-page xhr.onload = function () { var container = document.implementation.createHTMLDocument().documentElement; container.innerHTML = xhr.responseText; callback(container); }; xhr.send(null); } /** * Prevent event propagation. */ function stopPropagation(e) { e.stopPropagation(); } /** * Adds new items to the wardrobe, if action is '+', * or removes old items from the wardrobe, if action is '-' */ function updateWardrobe(selectItems, action, callback) { getWardrobe(function (oldWardrobe) { var oldWardrobeSet = new Set(oldWardrobe); var selectActions; var selectItemList = Array.from(selectItems); if (action == '+') { selectActions = selectItemList.filter(function (x) { return !oldWardrobeSet.has(x); }).map(function (x) { return action + x; }); } else if (action == '-') { selectActions = selectItemList.filter(function (x) { return oldWardrobeSet.has(x); }).map(function (x) { return action + x; }); } else { return; } jQuery.ajax({ type: 'POST', contentType: 'application/json; charset=utf-8', url: updateWardrobeURL, data: JSON.stringify({ 'select_actions': selectActions }) }).done(function () { if (callback) { callback(selectItemList, action); } Materialize.toast('Selections saved!', 4000); }); }); } var styleTags = [ 'gorgeous', 'simple', 'elegance', 'lively', 'mature', 'cute', 'sexy', 'pure', 'warm', 'cool' ]; var styleTagSet = new Set(styleTags); /** * A comparator to make sure style tags are returned in order. */ function styleTagComparator(x, y) { return styleTags.indexOf(x) - styleTags.indexOf(y); } var FilterableElement = /** @class */ (function () { function FilterableElement(element) { this.element = element; var titleElement = element.querySelector('span.title.truncate'); this.lowerCaseName = (titleElement && titleElement.textContent || '').trim().toLowerCase(); } ; FilterableElement.prototype.filter = function (text) { if (!text || this.lowerCaseName.indexOf(text) != -1) { this.element.style.display = 'block'; return true; } else { this.element.style.display = 'none'; return false; } }; ; return FilterableElement; }()); var FilterableSection = /** @class */ (function () { function FilterableSection(section, selector) { this.section = section; this.header = section.querySelector('div.collapsible-header'); this.body = section.querySelector('div.collapsible-body'); var itemElements = Array.from(section.querySelectorAll(selector)); this.items = itemElements.map(function (x) { return new FilterableElement(x); }); } ; FilterableSection.prototype.filter = function (text) { var count = 0; for (var i = 0; i < this.items.length; i++) { if (this.items[i].filter(text)) { count++; } } if (!text) { this.section.classList.remove('active'); this.header.classList.remove('active'); this.body.style.display = 'none'; this.section.style.display = 'block'; } else if (count > 0) { this.section.classList.add('active'); this.header.classList.add('active'); this.body.style.display = 'block'; this.section.style.display = 'block'; } else { this.section.classList.remove('active'); this.header.classList.remove('active'); this.body.style.display = 'none'; this.section.style.display = 'none'; } return count > 0; }; return FilterableSection; }()); var FilterableAccordion = /** @class */ (function () { function FilterableAccordion(selector, placeholder) { var container = document.querySelector('ul[data-collapsible]'); if (!container) { return; } var containerParentElement = container.parentElement; if (!containerParentElement) { return; } this.sections = Array.from(container.querySelectorAll('li')).map(function (x) { return new FilterableSection(x, selector); }); var filterHolder = document.createElement('div'); filterHolder.classList.add('filter-input'); var inputHolder = document.createElement('div'); inputHolder.classList.add('input-field'); this.inputField = document.createElement('input'); this.inputField.setAttribute('placeholder', placeholder); inputHolder.appendChild(this.inputField); this.inputField.onkeyup = _.debounce(FilterableAccordion.prototype.filter.bind(this), 500); filterHolder.appendChild(inputHolder); containerParentElement.insertBefore(filterHolder, container); } ; FilterableAccordion.prototype.filter = function () { var text = this.inputField.value.toLowerCase(); for (var i = 0; i < this.sections.length; i++) { this.sections[i].filter(text); } }; ; return FilterableAccordion; }()); var itemMetadata = {}; /** * Look up the item metadata using the wardrobe ID. */ function getCraftingIngredient(wid) { if (wid == 'future') { return new CraftingIngredient(wid, '#', 'Future Item', 's-future-item', null); } if (!(wid in itemMetadata)) { return null; } return new CraftingIngredient(wid, '/wardrobe/' + itemMetadata[wid]['href'], itemMetadata[wid]['name'], 's-' + itemMetadata[wid]['href'].replace('/', '-'), null); } var CraftingMetadata = /** @class */ (function () { function CraftingMetadata() { } return CraftingMetadata; }()); /** * Add a new prototype to describe a crafting path. */ var CraftingPath = /** @class */ (function () { function CraftingPath(pathElements) { if (pathElements === void 0) { pathElements = []; } this.pathElements = []; this.reversed = false; if (!pathElements || (pathElements.length == 0)) { return; } for (var i = 0; i < pathElements.length; i++) { var pathElement = pathElements[i]; this.pathElements.push(new CraftingIngredient(pathElement.wid, pathElement.href, pathElement.name, pathElement.icon, pathElement.needed)); } } ; CraftingPath.prototype.add = function (needed, next) { var newPath = new CraftingPath(this.pathElements); var pathElements = newPath.pathElements; if (pathElements.length > 0) { pathElements[pathElements.length - 1].needed = needed; } var ingredient = getCraftingIngredient(next); if (ingredient) { pathElements.push(ingredient); } return newPath; }; ; CraftingPath.prototype.item = function () { return this.reversed ? this.pathElements[0] : this.pathElements[this.pathElements.length - 1]; }; ; CraftingPath.prototype.wid = function () { return this.item().wid; }; ; CraftingPath.prototype.reverse = function () { var pathElements = this.pathElements; pathElements.reverse(); for (var i = 0; i < pathElements.length - 1; i++) { pathElements[i].needed = pathElements[i + 1].needed; } pathElements[pathElements.length - 1].needed = null; this.reversed = true; return this; }; ; return CraftingPath; }()); function pathCompare(a, b) { var aId = a.wid(); var bId = b.wid(); var aType = aId.charAt(0); var bType = bId.charAt(0); if (aType != bType) { return aType > bType ? 1 : -1; } var aNumber = parseInt(aId.substring(1)); var bNumber = parseInt(bId.substring(1)); return aNumber - bNumber; } var CraftingIngredient = /** @class */ (function () { function CraftingIngredient(wid, href, name, icon, needed) { this.wid = wid; this.href = href; this.name = name; this.icon = icon; this.needed = needed; } ; CraftingIngredient.prototype.getCraftedFromPaths = function () { var root = new CraftingPath(); var graph = new CraftingGraph(); var reversePaths = [root.add(0, this.wid)]; for (var i = 0; i < reversePaths.length; i++) { var path = reversePaths[i]; var wid1 = path.wid(); var crafting = graph.edges[wid1]; if (!crafting) { continue; } var keys2 = Object.keys(crafting); for (var j = 0; j < keys2.length; j++) { var wid2 = keys2[j]; var needed = crafting[wid2] || 0; reversePaths.push(path.add(needed, wid2)); } } reversePaths.sort(pathCompare); return reversePaths.map(function (x) { return x.reverse(); }); }; ; CraftingIngredient.prototype.getUsedToCraftPaths = function () { var root = new CraftingPath(); var paths = [root.add(0, this.wid)]; for (var i = 0; i < paths.length; i++) { var path = paths[i]; var wid1 = path.wid(); var crafting = itemMetadata[wid1]['crafting']; if (!crafting) { continue; } var keys2 = Object.keys(crafting); for (var j = 0; j < keys2.length; j++) { var wid2 = keys2[j]; var needed = crafting[wid2]; paths.push(path.add(needed, wid2)); } } paths.sort(pathCompare); var futureDesignMaterial = paths .map(function (x) { return itemMetadata[x.wid()]['crafting_tags']; }) .filter(function (x) { return x && new Set(x).has('future_design_material'); }).length != 0; if (futureDesignMaterial) { paths.push(root.add(0, 'future')); } return paths; }; ; return CraftingIngredient; }()); var CraftingDrop = /** @class */ (function () { function CraftingDrop(material, other) { this.material = material; this.other = Array.from(new Set(other)); if ((material.length == 0) && (other.length == 0)) { this.other.push('unknown'); } } CraftingDrop.prototype.getTotalMaterialCost = function () { var totalMaterialCost = {}; for (var j = 0; j < this.material.length; j++) { var dropItem = this.material[j]; var cost = dropItem.split(' '); var costAmount = cost[0]; var costType = cost[1]; totalMaterialCost[costType] = (totalMaterialCost[costType] || 0) + parseInt(costAmount); } return totalMaterialCost; }; return CraftingDrop; }()); var CraftingGraph = /** @class */ (function () { function CraftingGraph() { this.edges = {}; var keys1 = Object.keys(itemMetadata); for (var i = 0; i < keys1.length; i++) { var wid1 = keys1[i]; var crafting = itemMetadata[wid1]['crafting']; if (!crafting) { continue; } var keys2 = Object.keys(crafting); for (var j = 0; j < keys2.length; j++) { var wid2 = keys2[j]; var needed = crafting[wid2]; var innerEdge = this.edges[wid2]; if (!innerEdge) { this.edges[wid2] = innerEdge = {}; } innerEdge[wid1] = needed; } } } CraftingGraph.prototype.topologicalOrdering = function (wid, visited) { visited.add(wid); var ordering = []; var crafting = this.edges[wid] || {}; var keys = Object.keys(crafting); for (var i = 0; i < keys.length; i++) { if (!visited.has(keys[i])) { Array.prototype.push.apply(ordering, this.topologicalOrdering(keys[i], visited)); } } ordering.push(wid); return ordering; }; CraftingGraph.prototype.getCraftingDrops = function (wid) { var ordering = this.topologicalOrdering(wid, new Set()); var visited = {}; for (var i = 0; i < ordering.length; i++) { var outerWid = ordering[i]; var metadata = itemMetadata[outerWid]; var dropInfo = metadata['drops'] || []; var materialCost = dropInfo.filter(function (x) { return /^[0-9]* [A-Z]*$/.exec(x); }); var materialCostSet = new Set(materialCost); var dropLocations = dropInfo.filter(function (x) { return !materialCostSet.has(x); }); var maiden = dropLocations.filter(function (x) { return x.indexOf('maiden') == 0; }).length > 0; var princess = dropLocations.filter(function (x) { return x.indexOf('princess') == 0; }).length > 0; var goldCost = materialCost.filter(function (x) { return x.indexOf(' G') != -1; }); var otherCost = []; if (goldCost.length == 0) { if (maiden) { otherCost.push('maiden'); materialCost = []; } else if (princess) { otherCost.push('princess'); materialCost = []; } else { otherCost = dropLocations; } } else { materialCost = goldCost; } var crafting = this.edges[outerWid] || {}; var innerKeys = Object.keys(crafting); for (var j = 0; j < innerKeys.length; j++) { var innerWid = innerKeys[j]; var subcost = visited[innerWid]; var multiplier = crafting[innerWid] || 0; var subcostRecipe = subcost.material.filter(function (x) { return x.indexOf(' SC') != -1; }); var subcostNonRecipe = subcost.material.filter(function (x) { return x.indexOf(' SC') == -1; }); Array.prototype.push.apply(materialCost, subcostRecipe); for (var k = 0; k < multiplier; k++) { Array.prototype.push.apply(materialCost, subcostNonRecipe); } Array.prototype.push.apply(otherCost, subcost.other); } visited[outerWid] = new CraftingDrop(materialCost, otherCost); } return visited[wid]; }; return CraftingGraph; }()); var dropLabelIcons = { 'G': { 'name': 'Gold', 'location': 'Clothes Store', 'icon': '1/10/Gold.png' }, 'D': { 'name': 'Diamond', 'location': 'Clothes Store', 'icon': 'e/ea/Diamond.png' }, 'SC': { 'name': 'Starlight Coin', 'location': 'Store of Starlight', 'icon': '7/72/Starlight_Coin.png' }, 'CR': { 'name': 'Crystal Rose', 'location': 'Crystal Garden', 'icon': '4/42/Crystal_Rose.png' }, 'AC': { 'name': 'Association Coin', 'location': 'Association Store', 'icon': '9/9a/Association_Coin.png' }, 'SE': { 'name': 'Starwish Earrings', 'location': 'Association Fantasy Workshop', 'icon': '1/10/Starwish_Earrings.png' }, 'SH': { 'name': 'Starwish Hairpin', 'location': 'Association Fantasy Workshop', 'icon': '2/28/Starwish_Hairpin.png' }, 'SP': { 'name': 'Starwish Pendant', 'location': 'Association Fantasy Workshop', 'icon': 'f/fb/Starwish_Pendant.png' }, 'HR': { 'name': 'Hope Ring', 'location': 'Reconstruction', 'icon': '0/08/HopeRingIcon.PNG' }, 'RE': { 'name': 'Rebirth Earring', 'location': 'Reconstruction', 'icon': 'c/ce/RebirthEarringsIcon.PNG' }, 'EN': { 'name': 'Eternal Necklace', 'location': 'Reconstruction', 'icon': '1/12/EternalNecklaceIcon.PNG' }, 'J': { 'name': 'Jade', 'location': 'Porch of Misty', 'icon': '7/7e/Jade.png' }, 'DH': { 'name': 'Destiny Hourglass', 'location': 'Corridor of Clock', 'icon': 'c/c7/Destiny_Hourglass.png' }, 'SB': { 'name': 'Suzaku Bell', 'location': 'Tower of Zen', 'icon': '3/36/Suzaku_Bell.png' }, 'KC': { 'name': 'Karma Crystal', 'location': 'Time Yard', 'icon': '1/19/Karma_Crystal.png' }, 'CS': { 'name': 'Crystal Shoes', 'location': 'Room of Cinderella', 'icon': '3/3a/Crystal_Shoe.png' } }; /** * Replaces the drop information label with one that uses * an icon instead of letters, to make it easier to know * what it is visually. */ function updateDropLabelWithIcon(itemLabel, width) { var content = itemLabel.textContent || ''; content = content.trim(); var currencyMatcher = /^([0-9,]+)\s*([A-Z]+)$/.exec(content); if (!currencyMatcher) { return; } var currencyAmount = currencyMatcher[1]; var currencyType = currencyMatcher[2]; var currencyTitle = dropLabelIcons[currencyType]['name'] + ' (' + dropLabelIcons[currencyType]['location'] + ')'; var wikiaImageURL = 'https://vignette.wikia.nocookie.net/lovenikki/images/' + dropLabelIcons[currencyType]['icon'] + '/revision/latest/scale-to-height-down/' + width; itemLabel.innerHTML = '<img title="' + currencyTitle + '" src="' + wikiaImageURL + '"> ' + currencyAmount; } /** * Creates a span with the given cost information. */ function createDropLabel(costType, amount) { if (amount === void 0) { amount = null; } var span = document.createElement('span'); span.classList.add('di'); if (amount) { span.textContent = new Intl.NumberFormat('en-US').format(parseInt(amount)) + ' ' + costType; } else { span.textContent = costType; } updateDropLabelWithIcon(span, 16); return span; } /** * Add a filter to make it easier to find specific suits. */ function addDropsHelper() { new FilterableAccordion('a.witem', 'Filter items by name'); var itemLabels = Array.from(document.querySelectorAll('.witem .di')); for (var i = 0; i < itemLabels.length; i++) { updateDropLabelWithIcon(itemLabels[i], 20); } } var GuideItem = /** @class */ (function () { function GuideItem() { } return GuideItem; }()); /** * Retrieve the stage base score. */ function getBaseScore(stage, callback) { jQuery.ajax({ dataType: 'json', type: 'POST', contentType: 'application/json; charset=utf-8', url: 'https://my.nikkis.info/getguide/ln', data: JSON.stringify({ 'stage': stage }), success: function (data) { var guideItems = data['guide']['wardrobe']; callback(data['guide']['score'], guideItems); } }); } /** * Show the provided base score on the provided cell. */ function showBaseScore(cell, baseScore, guideItems) { var hasNew = guideItems.filter(function (x) { return x["new"]; }).length > 0; var content = cell.querySelector('.card-content'); var baseScoreContent = ''; if (hasNew) { content.classList.add('have-witem'); baseScoreContent = '<strong class="new-icon">New!</strong> '; } baseScoreContent += 'Base Score: ' + baseScore; var baseScoreHolder = document.createElement('p'); baseScoreHolder.classList.add('base-score'); baseScoreHolder.innerHTML = baseScoreContent; content.appendChild(baseScoreHolder); } /** * Show the base score for the given stylist arena stage. */ function addArenaStageHelper() { var cells = Array.from(document.querySelectorAll('.col')); for (var i = 0; i < cells.length; i++) { var cell = cells[i]; var guideElement = cell.querySelector('.card-action a'); if (!guideElement) { continue; } var stage = guideElement.href.substring(guideElement.href.lastIndexOf('/') + 1); getBaseScore('arena_common_' + stage, showBaseScore.bind(null, cell)); } } /** * Show the base score for the given commission stage. */ function showCommissionStageBaseScore(cell, stage) { if (!stage) { return; } if (cell.querySelector('.base-score')) { return; } getBaseScore('commission_common_' + stage, showBaseScore.bind(null, cell)); } /** * Filter the list of stages based on the percentage. */ function filterCommission(actRequest, container, input) { container.classList.add('active'); var body = container.querySelector('.collapsible-body'); body.style.display = 'block'; var cells = Array.from(body.querySelectorAll('.col')); var inputValue = parseInt(input.value || '0'); var begin = 0; var increment = 100 / cells.length; var end = increment; for (var i = 0; i < cells.length; i++) { var cell = cells[i]; if ((inputValue >= Math.floor(begin)) && (inputValue <= Math.floor(end))) { cell.style.display = ''; var titleElement = cell.querySelector('.card-title'); var stage = titleElement.textContent; showCommissionStageBaseScore(cell, stage); } else { cell.style.display = 'none'; } begin += increment; end += increment; } } ; /** * Rather than having you click into each stage to figure out where you are at, * enter in all the percentages so you can know immediately, and see what your * base score is for the given stage. */ function addCommissionStageHelper() { var actRequests = [ [4000, 4500, 5000, 5000, 5000, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000], [4000, 4500, 4500, 5000, 5500, 5500, 6000] ]; var headers = document.querySelectorAll('.collapsible .collapsible-header'); for (var i = 0; i < headers.length; i++) { var container = document.createElement('div'); container.classList.add('stage-progress'); var input = document.createElement('input'); input.setAttribute('type', 'text'); input.addEventListener('click', stopPropagation); input.addEventListener('keypress', _.debounce(filterCommission.bind(null, actRequests[i], headers[i].parentElement, input), 500)); container.appendChild(input); var percent = document.createElement('div'); percent.innerHTML = ' %'; container.appendChild(percent); headers[i].appendChild(container); } } /** * Add a helper to the custom stage page. */ function addCustomStageHelper() { var selectWrappers = Array.from(document.querySelectorAll('div.select-wrapper')); if (selectWrappers.length == 0) { setTimeout(addCustomStageHelper, 100); return; } if (document.location.hash) { loadCustomGuide(); } } /** * Updates the sort order for accessories. */ function updateMyNiGuideSortOrder(items) { var sortRemap = { 1: 1101, 2: 2101, 3: 3101, 4: 4101, 5: 5101, 6: 6101, 7: 6201, 8: 7001, 10: 8101, 20: 8102, 28: 8103, 29: 8104, 11: 8201, 12: 8301, 13: 8302, 14: 8401, 15: 8402, 16: 8403, 17: 8501, 18: 8502, 33: 8503, 19: 8601, 21: 8701, 22: 8702, 23: 8703, 24: 8704, 25: 8705, 26: 8706, 27: 8707, 30: 8708, 31: 8709, 32: 8801, 9: 9101, 34: 10101 }; for (var i = 0; i < items.length; i++) { var item = items[i]; var subSortString = item.getAttribute('data-sortsub'); var subSort = parseInt(subSortString); var gameSort = sortRemap[subSort] || 20000; item.setAttribute('data-sortgame', gameSort.toString()); } var container = document.getElementById('myni-guide-sort'); container.appendChild(document.createTextNode(' or ')); var sortLink = document.createElement('a'); sortLink.setAttribute('href', '#!'); sortLink.onclick = guide_sort.bind(null, 'myni-guide', 'data-sortgame'); sortLink.textContent = 'game order'; container.appendChild(sortLink); } /** * Compare two different grades. */ function attachClothingAttributes(item, stageAttributes, container) { var table = container.querySelector('.attr-table'); var attributes = Array.from(table.querySelectorAll('.cloth-attr')).map(function (x) { return (x.textContent || '').trim(); }); var grades = Array.from(table.querySelectorAll('.cloth-grade')).map(function (x) { return (x.textContent || '').trim(); }); var attributeMetadataElement = document.createElement('span'); attributeMetadataElement.classList.add('attr-metadata'); attributeMetadataElement.classList.add('grey-text'); attributeMetadataElement.classList.add('text-darken1'); for (var i = 0; i < attributes.length; i++) { if (!stageAttributes.has(attributes[i])) { continue; } var span = document.createElement('span'); span.classList.add('di'); span.textContent = attributes[i] + ': ' + grades[i]; attributeMetadataElement.appendChild(span); } var annotation = item.querySelector('span.item-annot'); if (!annotation) { annotation = document.createElement('span'); annotation.classList.add('item-annot'); item.appendChild(annotation); } annotation.appendChild(attributeMetadataElement); } /** * Adds usability improvements to MyNi guide. */ function addStageHelper() { var guide = document.getElementById('myni-guide'); if (!guide) { setTimeout(addStageHelper, 1000); return; } var items = Array.from(guide.querySelectorAll('.witem')); if (items.length == 0) { setTimeout(addStageHelper, 1000); return; } updateMyNiGuideSortOrder(items); var checkAttributesButton = createButton('Check Stage-Matching Attributes', function () { checkAttributesButton.remove(); var table = document.querySelector('.attr-table'); var stageAttributes = new Set(Array.from(table.querySelectorAll('.cloth-attr')).map(function (x) { return (x.textContent || '').trim(); })); for (var i = 0; i < items.length; i++) { var item = items[i]; var title = item.querySelector('.title'); var href = title.getAttribute('href'); processXMLHttpRequest(href, attachClothingAttributes.bind(null, item, stageAttributes)); } }); var guideParentElement = guide.parentElement; guideParentElement.insertBefore(checkAttributesButton, guide); } /** * Update suit selections. */ function updateSuitSelections(selectItemList) { var updatedSuits = {}; for (var i = 0; i < selectItemList.length; i++) { var item = document.querySelector('img[wid="' + selectItemList[i] + '"]'); if (!item) { continue; } item.classList.add('have-suit-part'); item.setAttribute('src', '/img/suit_dia_has.png'); var suit = item.closest('a'); var href = suit.getAttribute('href'); updatedSuits[href] = suit; } var keys = Object.keys(updatedSuits); for (var i = 0; i < keys.length; i++) { var suit = updatedSuits[keys[i]]; suit.classList.remove('have-witem'); suit.classList.add('have-sitem'); } } /** * Adds an annotation to the suit describing one of its cost elements. */ function addSuitAnnotation(suit, costType) { var amount = suit.getAttribute('data-cost-' + costType); if (!amount) { return; } var annotation = suit.querySelector('.secondary-content .suit-metadata'); if (!annotation) { annotation = document.createElement('div'); annotation.classList.add('suit-metadata'); var container = suit.querySelector('.secondary-content'); container.appendChild(annotation); } annotation.appendChild(createDropLabel(costType, amount)); } /** * Adds annotations to all suits describing all of its cost elements, using the * provided drops page content. */ function annotateStoreSuitItems(e) { getWardrobe(function (wardrobe) { var wardrobeSet = new Set(wardrobe); var pieces = Array.from(document.querySelectorAll('img[wid]')); for (var i = 0; i < pieces.length; i++) { var piece = pieces[i]; var wid = piece.getAttribute('wid'); var metadata = itemMetadata[wid]; if (!metadata) { continue; } var dropItems = metadata['drops']; if (!dropItems) { continue; } var haveSuitPart = wardrobeSet.has(wid); if (haveSuitPart) { continue; } var suit = piece.closest('a'); dropItems = dropItems.filter(function (x) { return /^[0-9]* [A-Z]*$/.exec(x); }); for (var j = 0; j < dropItems.length; j++) { var dropItem = dropItems[j]; var cost = dropItem.split(' '); var costAmount = cost[0]; var costType = cost[1]; if (costType == 'SC') { continue; } piece.classList.add('store'); var oldCost = parseInt(suit.getAttribute('data-cost-' + costType) || '0'); var newCost = oldCost + parseInt(costAmount); suit.setAttribute('data-cost-' + costType, newCost.toString()); } } var keys = Array.from(Object.keys(dropLabelIcons)); var querySelector = Array.from(keys).map(function (x) { return 'a[data-cost-' + x + ']'; }).concat(['a[data-cost-tags]']).join(','); var dropSuits = Array.from(document.querySelectorAll(querySelector)); for (var i = 0; i < dropSuits.length; i++) { for (var j = 0; j < keys.length; j++) { addSuitAnnotation(dropSuits[i], keys[j]); } addSuitAnnotation(dropSuits[i], 'tags'); } var button = e.target; button.remove(); }); } /** * Update suit pieces selections (on the single suit page). */ function updateSuitPiecesSelections(selectItemList, action) { updateWardrobeSelections(null, null, selectItemList, action); var suit = document.querySelector('div.sitem'); if (!suit) { return; } if (action == '+') { suit.classList.add('have-sitem'); } else if (action == '-') { suit.classList.remove('have-sitem'); } } /** * Adds the suit to the wardrobe. */ function addSuitToWardrobe() { var suitItems = Array.from(document.querySelectorAll('div.witem')); var suitPieces = suitItems.map(function (x) { return x.getAttribute('wid'); }); updateWardrobe(new Set(suitPieces), '+', updateSuitPiecesSelections); } /** * Adds the suit to the wardrobe. */ function removeSuitFromWardrobe() { var suitItems = Array.from(document.querySelectorAll('div.witem')); var suitPieces = suitItems.map(function (x) { return x.getAttribute('wid'); }); updateWardrobe(new Set(suitPieces), '-', updateSuitPiecesSelections); } /** * Adds the crafting cost to all items. */ function annotateSuitMaterialCost(graph, suitContainer, e) { var items = Array.from(suitContainer.querySelectorAll('.witem')); var totalSuitMaterialCost = {}; var totalSuitOtherCost = new Set(); var itemWids = items.map(function (x) { return x.getAttribute('wid'); }); for (var i = 0; i < items.length; i++) { var wid = items[i].getAttribute('wid'); var dropInfo = graph.getCraftingDrops(wid); var otherCost = dropInfo.other; var totalMaterialCost = dropInfo.getTotalMaterialCost(); var container = items[i].querySelector('p'); container.classList.add('drop-metadata'); for (var j = container.childNodes.length - 1; j >= 0; j--) { container.childNodes[j].remove(); } var costTypes = Object.keys(totalMaterialCost).sort(); for (var j = 0; j < costTypes.length; j++) { var costType = costTypes[j]; var amount = totalMaterialCost[costType]; container.appendChild(createDropLabel(costType, amount.toString())); totalSuitMaterialCost[costType] = (totalSuitMaterialCost[costType] || 0) + amount; } for (var j = 0; j < otherCost.length; j++) { container.appendChild(createDropLabel(otherCost[j])); totalSuitOtherCost.add(otherCost[j]); } } var container = document.createElement('div'); container.classList.add('drop-metadata'); var label = document.createElement('strong'); label.textContent = 'Approximate Value'; container.appendChild(label); container.appendChild(document.createTextNode(' (excluding dyes): ')); var costTypes = Object.keys(totalSuitMaterialCost).sort(); for (var j = 0; j < costTypes.length; j++) { var costType = costTypes[j]; var amount = totalSuitMaterialCost[costType]; container.appendChild(createDropLabel(costType, amount.toString())); } var totalSuitOtherCostArray = Array.from(totalSuitOtherCost); for (var j = 0; j < totalSuitOtherCostArray.length; j++) { container.appendChild(createDropLabel(totalSuitOtherCostArray[j])); } var button = e.target; var buttonParentElement = button.parentElement; buttonParentElement.replaceChild(container, button); } /** * Add a helper to the page to make it easier to add a whole suit to your wardrobe. */ function addSuitHelper() { getWardrobe(function (wardrobe) { var wardrobeSet = new Set(wardrobe); var submitHolder = document.createElement('div'); var available = Array.from(document.querySelectorAll('div.witem')); var current = available.filter(function (x) { return wardrobeSet.has(x.getAttribute('wid')); }); submitHolder.appendChild(createButton('Add to Wardrobe', addSuitToWardrobe)); submitHolder.appendChild(document.createTextNode(' ')); submitHolder.appendChild(createButton('Remove from Wardrobe', removeSuitFromWardrobe)); var checkHolder = document.createElement('div'); checkHolder.appendChild(createButton('Approximate Value', annotateSuitMaterialCost.bind(null, new CraftingGraph(), document.documentElement))); var headerContainer = document.querySelector('.container > .collection'); var headerContainerParentElement = headerContainer.parentElement; headerContainerParentElement.insertBefore(submitHolder, headerContainer); var collectionContainer = document.querySelector('.section > .collection'); var collectionContainerParentElement = collectionContainer.parentElement; collectionContainerParentElement.insertBefore(checkHolder, collectionContainer); }); } /** * Adds selected suits to the wardrobe. */ function addSuitsToWardrobe() { var newSuits = Array.from(document.querySelectorAll('a[href].sitem.have-witem')); var newItems = Array.from(newSuits) .map(function (x) { return Array.from(x.querySelectorAll('.witem')).map(function (y) { return y.getAttribute('wid'); }); }) .reduce(function (array, x) { return array.concat(x); }, []); updateWardrobe(new Set(newItems), '+', updateSuitSelections); } /** * Add a marker to remember that the user wanted to add the selected suit * to their wardrobe. */ function markSuitInWardrobe(e) { e.preventDefault(); var target = e.target; if ((target.tagName.toUpperCase() != 'A') || !target.href || (!target.classList.contains('sitem'))) { target = target.closest('a[href].sitem'); } if (target.classList.contains('have-sitem')) { return; } if (target.classList.contains('have-witem')) { target.classList.remove('have-witem'); } else { target.classList.add('have-witem'); } } /** * Add a filter to make it easier to find specific suits. */ function addSuitsHelper() { new FilterableAccordion('a.sitem', 'Filter suits by name'); var modeContainer = document.createElement('div'); modeContainer.classList.add('fixed-action-btn'); var modeToggle = document.createElement('a'); modeToggle.setAttribute('id', 'save-button'); modeToggle.classList.add('btn-floating'); modeToggle.classList.add('btn-large'); modeToggle.classList.add('waves-effect'); modeToggle.classList.add('waves-light'); modeToggle.classList.add('scale-transition'); var modeToggleText = document.createElement('i'); modeToggleText.setAttribute('id', 'save-button-text'); modeToggleText.classList.add('material-icons'); modeToggleText.textContent = 'turned_in_not'; modeToggle.addEventListener('click', function (x) { var suits = document.querySelectorAll('a[href].sitem'); if (modeToggleText.textContent == 'turned_in_not') { modeToggleText.textContent = 'save'; modeToggle.classList.add('pink'); modeToggle.classList.add('lighten-2'); for (var i = 0; i < suits.length; i++) { suits[i].addEventListener('click', markSuitInWardrobe); } } else { addSuitsToWardrobe(); } }); modeToggle.appendChild(modeToggleText); modeContainer.appendChild(modeToggle); var modeContainerContainer = document.querySelector('.container'); modeContainerContainer.appendChild(modeContainer); var checkHolder = document.createElement('div'); checkHolder.appendChild(createButton('Store Completion', annotateStoreSuitItems)); var collectionContainer = document.querySelector('.container > ul.collapsible'); var collectionContainerParentElement = collectionContainer.parentElement; collectionContainerParentElement.insertBefore(checkHolder, collectionContainer); } function exportItemAsCSVRow(container) { var wid = container.getAttribute('wid'); var metadata = window.tag_data[wid]; var name = metadata.name; var type = metadata.type; var tags = metadata.tags; var rarity = tags.filter(function (x) { return x.indexOf('rare ') == 0; })[0]; var style = tags.filter(function (x) { return styleTagSet.has(x); }).sort(styleTagComparator); return [wid, name, type, rarity].concat(style).join(','); } /** * Export the visible items as a CSV file */ function exportAsCSV() { var csvHeader = 'id,name,type,rarity,style,style,style,style,style'; var csvContent = Array.from(document.querySelectorAll('#item-list div[wid]')).map(exportItemAsCSVRow).join('\n'); var blob = new Blob([csvHeader + '\n' + csvContent], { type: 'text/csv' }); var link = document.createElement('a'); link.href = URL.createObjectURL(blob); var hash = document.location.hash || '#'; link.download = hash.substring(1) + '.csv'; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * Update auto complete with the new set of autocomplete options. */ function updateAutocomplete(autocompletes) { var hash = document.location.hash || '#'; var autoCompleteOptionsData = Array.from(autocompletes).reduce(function (options, x) { options[x] = null; return options; }, {}); var chipData = hash.substring(1).split(/\+|\%20/gi).filter(function (x) { return x; }).map(function (x) { return { 'tag': x.replace(/_/gi, ' ').trim() }; }); jQuery('.chips-autocomplete').material_chip({ autocompleteOptions: { data: autoCompleteOptionsData, limit: Infinity, minLength: 1 }, placeholder: 'Enter a tag', secondaryPlaceholder: '+tag', data: chipData }); window.filterTags(true); } /** * Add drop metadata to the visible items. */ function addDropMetadata() { var items = window.tag_data; var keys = Object.keys(items); var containers = document.querySelectorAll('div[wid]'); for (var i = 0; i < containers.length; i++) { var container = containers[i]; var key = container.getAttribute('wid'); if (!(key in items)) { continue; } var dropMetadataElement = document.createElement('span'); dropMetadataElement.classList.add('drop-metadata'); dropMetadataElement.classList.add('grey-text'); dropMetadataElement.classList.add('text-darken1'); if (items[key].drop_metadata) { for (var j = 0; j < items[key].drop_metadata.length; j++) { dropMetadataElement.appendChild(createDropLabel(items[key].drop_metadata[j])); } } var annotation = container.querySelector('span.item-annot'); if (!annotation) { annotation = document.createElement('span'); annotation.classList.add('item-annot'); container.appendChild(annotation); } annotation.appendChild(dropMetadataElement); } } /** * Add additional tags to use for search. */ function addTagsHelper() { var items = window.tag_data; var metadataKeys = Object.keys(itemMetadata); for (var i = 0; i < metadataKeys.length; i++) { var wid = metadataKeys[i]; if (!(wid in items)) { continue; } Array.prototype.push.apply(items[wid].tags, itemMetadata[wid]['drop_tags']); items[wid]['drop_metadata'] = itemMetadata[wid].drops || []; } var tagKeys = Object.keys(items); var autocompletes = []; for (var i = 0; i < tagKeys.length; i++) { var wid = tagKeys[i]; if (items[wid].tags.indexOf('future design material') == -1) { items[wid].tags.push('not future design material'); } Array.prototype.push.apply(autocompletes, items[wid].tags); } setTimeout(updateAutocomplete.bind(null, new Set(autocompletes)), 1000); var exportButton = createButton('Export as CSV', exportAsCSV); var itemList = document.getElementById('item-list'); var itemListParentElement = itemList.parentElement; itemListParentElement.insertBefore(exportButton, itemList); } /** * Returns the numbers typed into the input field. */ function getSelectedNumbers(insertByNumberInputField) { var typedNumbers = insertByNumberInputField.value.split(/[, ]+/gi); var selectedNumbers = []; for (var i = 0; i < typedNumbers.length; i++) { var numberString = typedNumbers[i]; if (numberString == '') { continue; } var pos = numberString.indexOf('-'); if (pos != -1) { var start = parseInt(numberString.substring(0, pos)); var end = parseInt(numberString.substring(pos + 1)); for (var j = start; j <= end; j++) { selectedNumbers.push(j); } } else { selectedNumbers.push(parseInt(numberString)); } } return selectedNumbers; } /** * Updates the margins around wardrobe items so you can scan through * the list 12 items at a time, instead of 2 items at a time. */ function updateWardrobeMargin(ownedFilterElement, notOwnedFilterElement) { var visibleItems = null; if (ownedFilterElement.classList.contains('filter-selected')) { visibleItems = Array.from(document.querySelectorAll('.have-witem, .have-sitem')); } else if (notOwnedFilterElement.classList.contains('filter-selected')) { visibleItems = Array.from(document.querySelectorAll('.witem:not(.have-witem):not(.have-sitem)')); } else { visibleItems = Array.from(document.querySelectorAll('.witem')); } for (var i = 0; i < visibleItems.length; i++) { visibleItems[i].style.marginBottom = (i % 12 > 9) ? '2em' : '0px'; } } /** * Make it easier to add items by unhiding anything that you list. */ function markItemInWardrobe(ownedFilterElement, notOwnedFilterElement, insertByNumberInputField, event) { if (event && event.keyCode != 188) { return; } var owned = ownedFilterElement.classList.contains('filter-selected'); var notOwned = notOwnedFilterElement.classList.contains('filter-selected'); var type = pathParts[2]; var selectedNumbers = getSelectedNumbers(insertByNumberInputField); var checkSelectedNumbers = new Set(selectedNumbers); var selector = owned ? 'a.witem.filtered-out' : notOwned ? 'a.witem:not(.filtered-out)' : 'a.witem'; var checkItems = Array.from(document.querySelectorAll(selector)); for (var i = 0; i < checkItems.length; i++) { var href = checkItems[i].getAttribute('href'); if (href == null) { continue; } var itemId = parseInt(href.substring('/wardrobe/'.length + type.length + 1)); if (notOwned) { if (checkSelectedNumbers.has(itemId)) { checkItems[i].style.display = 'none'; checkItems[i].classList.add('have-sitem'); } else { checkItems[i].style.display = ''; checkItems[i].classList.remove('have-sitem'); } } else { if (checkSelectedNumbers.has(itemId)) { if (owned) { checkItems[i].style.display = ''; } checkItems[i].classList.add('have-sitem'); } else { if (owned) { checkItems[i].style.display = 'none'; } checkItems[i].classList.remove('have-sitem'); } } } updateWardrobeMargin(ownedFilterElement, notOwnedFilterElement); if (owned || notOwned) { var visibleItem = null; var reverseSortedItemIds = selectedNumbers.sort(function (a, b) { return b - a; }); for (var j = 0; j < reverseSortedItemIds.length; j++) { var newItemId = reverseSortedItemIds[j]; var newItem = document.querySelector('a[href="/wardrobe/' + type + '/' + newItemId + '"]'); if (newItem) { visibleItem = newItem; break; } } if (visibleItem) { var visibleItems = Array.from(document.querySelectorAll(owned ? '.have-witem, .have-sitem' : '.witem:not(.have-witem):not(.have-sitem)')); for (var i = 0; i < visibleItems.length; i++) { if (visibleItems[i] == visibleItem) { visibleItems[i - (i % 12)].scrollIntoView(); var navbar = document.querySelector('.navbar-fixed'); window.scrollTo(window.scrollX, window.scrollY - navbar.clientHeight); break; } } } } } /** * Update wardrobe selections. */ function updateWardrobeSelections(ownedFilterElement, notOwnedFilterElement, selectItemList, action) { var owned = !ownedFilterElement || ownedFilterElement.classList.contains('filter-selected'); var notOwned = notOwnedFilterElement && notOwnedFilterElement.classList.contains('filter-selected'); for (var i = 0; i < selectItemList.length; i++) { var attributeSelector = '[wid="' + selectItemList[i] + '"]'; var items = document.querySelectorAll('a' + attributeSelector + ', div' + attributeSelector); for (var j = 0; j < items.length; j++) { var item = items[j]; item.classList.remove('have-sitem'); if (action == '+') { item.classList.add('have-witem'); item.classList.remove('filtered-out'); if (owned) { item.style.display = ''; } else if (notOwned) { item.style.display = 'none'; } } else if (action == '-') { item.classList.remove('have-witem'); item.classList.add('filtered-out'); if (owned) { item.style.display = 'none'; } else if (notOwned) { item.style.display = ''; } } } } var currentCountContainer = document.querySelector('.current-count'); if (currentCountContainer) { var wardrobeCount = document.querySelectorAll('.have-witem').length; currentCountContainer.textContent = wardrobeCount + ' matching item' + (wardrobeCount == 1 ? '' : 's'); } if (ownedFilterElement && notOwnedFilterElement) { updateWardrobeMargin(ownedFilterElement, notOwnedFilterElement); } } /** * Checks the wardrobe against the specified text area, and then * update with the new values. */ function checkWardrobe(ownedFilterElement, notOwnedFilterElement, insertByNumberInputField, action) { var type = pathParts[2]; var selectedNumbers = getSelectedNumbers(insertByNumberInputField); var selectedWIDs = selectedNumbers .map(function (x) { return document.querySelector('a[href="/wardrobe/' + type + '/' + x + '"]'); }) .filter(function (x) { return x; }) .map(function (x) { return x.getAttribute('wid'); }); var selectValues = new Set(selectedWIDs); if (selectValues.size == 0) { return; } updateWardrobe(selectValues, action, updateWardrobeSelections.bind(null, ownedFilterElement, notOwnedFilterElement)); } ; /** * Get the wardrobe as a string. */ function getWardrobeShorthand(current, available) { if (current.length == 0) { return ''; } var matchIndex = -1; var matchStart = -1; var matches = []; for (var i = 0; i < available.length; i++) { if (available[i] == current[matchIndex + 1]) { matchIndex++; if (matchStart == -1) { matchStart = matchIndex; } } else if (matchStart != -1) { var newMatch = [current[matchStart].toString()]; if (matchIndex != matchStart) { newMatch.push(matchIndex == matchStart + 1 ? ',' : '-'); newMatch.push(current[matchIndex].toString()); } matches.push(newMatch.join('')); matchStart = -1; } } return matches.join(','); } /** * Annotate */ function checkSafeDecompose() { filter_Owned(); getWardrobe(function (wardrobe) { var wardrobeSet = new Set(wardrobe); var itemElements = Array.from(document.querySelectorAll('.have-witem')); for (var i = 0; i < itemElements.length; i++) { var itemElement = itemElements[i]; var wid = itemElement.getAttribute('wid'); var item = getCraftingIngredient(wid); var annotation; if (item == null) { annotation = 'N/A'; itemElement.classList.add('safe-decompose'); } else { var paths = item.getUsedToCraftPaths(); if (paths.length == 1) { annotation = 'N/A'; itemElement.classList.add('safe-decompose'); } else { var haveCount = 0; var futureDesignMaterial = false; for (var j = 1; j < paths.length; j++) { if (paths[j].wid() == 'future') { futureDesignMaterial = true; } else if (wardrobeSet.has(paths[j].wid())) { haveCount++; } } if (haveCount > 0) { --haveCount; } if (futureDesignMaterial) { annotation = 'future'; } else { annotation = haveCount + '/' + (paths.length - 1); if (haveCount == (paths.length - 1)) { itemElement.classList.add('safe-decompose'); } } } } var annotationElement = document.createElement('div'); annotationElement.textContent = annotation; var annotationHolder = itemElement.querySelector('.secondary-content'); annotationHolder.appendChild(annotationElement); } }); } /** * Add a helper to the page to make it easier to update your wardrobe by number. */ function addWardrobeHelper() { var wardrobeManager = document.getElementById('wardrobe-manager'); if (!wardrobeManager) { return; } var wardrobeManagerParentNode = wardrobeManager.parentNode; if (!wardrobeManagerParentNode) { return; } var ownedFilterElement = document.getElementById('filter-owned'); var notOwnedFilterElement = document.getElementById('filter-notowned'); if (!ownedFilterElement || !notOwnedFilterElement) { return; } getWardrobe(function (wardrobe) { var currentWardrobe = wardrobe .map(function (x) { return document.querySelector('a[wid="' + x + '"]'); }) .filter(function (x) { return x; }) .map(function (x) { return parseInt(x.href.split('/')[5]); }) .sort(function (a, b) { return a - b; }); var itemElements = Array.from(document.querySelectorAll('a[wid]')); var availableWardrobe = itemElements.map(function (x) { return parseInt(x.href.split('/')[5]); }).sort(function (a, b) { return a - b; }); var currentWardrobeShorthand = getWardrobeShorthand(currentWardrobe, availableWardrobe); var insertByNumberContainer = document.createElement('div'); insertByNumberContainer.classList.add('insert-by-number'); var oldWardrobeInputField = document.createElement('input'); oldWardrobeInputField.setAttribute('type', 'hidden'); oldWardrobeInputField.value = currentWardrobeShorthand; insertByNumberContainer.appendChild(oldWardrobeInputField); var currentCountContainer = document.createElement('div'); currentCountContainer.classList.add('current-count'); currentCountContainer.textContent = currentWardrobe.length + ' matching item' + (currentWardrobe.length == 1 ? '' : 's'); insertByNumberContainer.appendChild(currentCountContainer); var insertByNumberInputField = document.createElement('textarea'); var boundMarkItemInWardrobe = markItemInWardrobe.bind(null, ownedFilterElement, notOwnedFilterElement, insertByNumberInputField); insertByNumberInputField.onkeyup = _.debounce(boundMarkItemInWardrobe, 500); insertByNumberInputField.setAttribute('placeholder', 'Enter numbers (1,2,3) and number ranges (1-3,4-7)'); insertByNumberContainer.appendChild(insertByNumberInputField); var submitHolder = document.createElement('div'); submitHolder.appendChild(createButton('Add to Wardrobe', checkWardrobe.bind(null, ownedFilterElement, notOwnedFilterElement, insertByNumberInputField, '+'))); submitHolder.appendChild(document.createTextNode(' ')); submitHolder.appendChild(createButton('Remove from Wardrobe', checkWardrobe.bind(null, ownedFilterElement, notOwnedFilterElement, insertByNumberInputField, '-'))); insertByNumberContainer.appendChild(submitHolder); wardrobeManagerParentNode.insertBefore(insertByNumberContainer, wardrobeManager); var updateMarginAndFocus = function (e) { boundMarkItemInWardrobe(); updateWardrobeMargin(ownedFilterElement, notOwnedFilterElement); insertByNumberInputField.focus(); }; ownedFilterElement.addEventListener('click', updateMarginAndFocus); notOwnedFilterElement.addEventListener('click', updateMarginAndFocus); var safeDecomposeButton = createButton('Check for Safe Decompose (Experimental)', checkSafeDecompose); wardrobeManagerParentNode.insertBefore(safeDecomposeButton, insertByNumberContainer); }); } /** * Combine paths leading to the same item into one multi path. */ function combinePaths(multiPaths, path) { var lastMultiPath = (multiPaths.length > 0) ? multiPaths[multiPaths.length - 1] : null; if (lastMultiPath && (path.wid() == lastMultiPath[0].wid())) { lastMultiPath.push(path); } else { multiPaths.push([path]); } return multiPaths; } /** * Convert crafting item metadata into an HTML list. */ function showCraftingPath(link, list, item, multiPath, e) { if (link.classList.contains('active')) { return true; } if (!list.classList.contains('show-path')) { list.classList.add('show-path'); } var actives = list.querySelectorAll('.active'); for (var i = 0; i < actives.length; i++) { actives[i].classList.remove('active'); } link.classList.add('active'); var listParentElement = list.parentElement; var placeholder = listParentElement.querySelector('.crafting-path'); var containers = document.createElement('div'); containers.classList.add('crafting-path'); for (var i = 0; i < multiPath.length; i++) { var path = multiPath[i]; var container = document.createElement('div'); var pathElements = path.pathElements; for (var j = 0; j < pathElements.length; j++) { if (j > 0) { var span = document.createElement('span'); span.classList.add('needed'); var needed = pathElements[j - 1].needed; span.innerHTML = '× ' + needed + '<br/><span class="arrow">→</span>'; container.appendChild(span); } var item = pathElements[j]; container.appendChild(createItemLink(item.href, item.name, item.wid, item.icon, false)); } containers.appendChild(container); } var placeHolderParentElement = placeholder.parentElement; placeHolderParentElement.replaceChild(containers, placeholder); e.stopPropagation(); return false; } /** * Generates a link for the given item. */ function createItemLink(href, name, wid, icon, hasOnClick) { var link = document.createElement('a'); link.classList.add('witem'); link.classList.add('icon-room'); if (hasOnClick) { link.setAttribute('href', '#!'); link.setAttribute('data-href', href); } else { link.setAttribute('href', href); } link.setAttribute('title', name); link.setAttribute('alt', name); var outerIcon = document.createElement('div'); outerIcon.classList.add('icon'); if (wid == 'future') { outerIcon.textContent = name; } else { link.setAttribute('wid', wid); } var innerIcon = document.createElement('div'); innerIcon.classList.add('inner-icon'); innerIcon.classList.add(icon); outerIcon.appendChild(innerIcon); link.appendChild(outerIcon); return link; } /** * Convert crafting item metadata into an HTML list. */ function formatCraftingMetadata(list, multiPath, wardrobeSet) { var item = multiPath[0].item(); var link = createItemLink(item.href, item.name, item.wid, item.icon, true); if (wardrobeSet.has(item.wid)) { link.classList.add('have-witem'); } if (item.wid != 'future') { link.onclick = showCraftingPath.bind(null, link, list, item, multiPath); } list.appendChild(link); } /** * Add a helper to the page to search the crafting tree in the forward * direction (everything this item can be used to craft). */ function addWardrobeItemCraftingSection(sectionName, item, pathGenerator) { var referenceElement = getSection(document.documentElement, 'Obtained from'); if (!referenceElement) { referenceElement = document.querySelector('.fixed-action-btn'); } var section = getSection(document.documentElement, sectionName, referenceElement); var checkDependenciesButton = createButton('Identify All', function () { var container = document.createElement('div'); container.classList.add('crafting-tree'); var loading = document.createElement('div'); loading.textContent = 'Identifying...'; container.appendChild(loading); section.replaceChild(container, checkDependenciesButton); for (var i = section.childNodes.length - 1; i >= 0; i--) { var childNode = section.childNodes[i]; if (childNode.nodeType == 3) { childNode.remove(); continue; } if (childNode.nodeType != 1) { continue; } var childElement = childNode; if (childElement.classList.contains('crafting-tree')) { continue; } else if ('H5' == childElement.tagName.toUpperCase()) { continue; } else { childElement.style.display = 'none'; } } var placeholder1 = document.createElement('div'); placeholder1.classList.add('transitive-dependencies'); container.appendChild(placeholder1); var placeholder2 = document.createElement('div'); placeholder2.classList.add('crafting-path'); container.appendChild(placeholder2); getWardrobe(function (wardrobe) { var wardrobeSet = new Set(wardrobe); var list = document.createElement('div'); list.classList.add('transitive-dependencies'); var paths = pathGenerator(); var multiPaths = paths.reduce(combinePaths, []); for (var i = 0; i < multiPaths.length; i++) { if (multiPaths[i][0].wid() != item.wid) { formatCraftingMetadata(list, multiPaths[i], wardrobeSet); } } if (list.childNodes.length == 0) { loading.innerHTML = 'Nothing'; } else { var descriptor = null; if (sectionName == 'Used to craft') { loading.innerHTML = 'Touch an item below to see the crafting path, which will show why <strong>' + item.name + '</strong> is needed to make it!'; } else { loading.innerHTML = 'Touch an item below to see the crafting path, which will show how it is used to make <strong>' + item.name + '</strong>!'; } } container.replaceChild(list, placeholder1); }); }); var collection = section.querySelector('.collection'); if (collection) { section.insertBefore(checkDependenciesButton, collection); } else { section.appendChild(checkDependenciesButton); } } /** * Add a helper to the page to search the crafting tree. */ function addWardrobeItemHelper() { var itemElement = document.querySelector('.collection .witem'); var wid = itemElement.getAttribute('wid'); var title = itemElement.querySelector('.title'); var href = null, name = null; if (title) { href = title.getAttribute('href') || ''; name = (title.textContent || '').trim(); } else { href = document.location.pathname; var headerContainer = itemElement.closest('.container'); var header = headerContainer.querySelector('h4'); name = (header.childNodes[0].textContent || '').trim(); } var iconElement = itemElement.querySelector('.inner-icon'); var icon = Array.from(iconElement.classList).filter(function (x) { return x.indexOf('s-') == 0; })[0]; var metadata = itemMetadata[wid]; if (!metadata) { return; } var item = new CraftingIngredient(wid, href, name, icon, 1); var section = getSection(document.documentElement, 'Tags'); if (section) { var dropTags = metadata['drop_tags'] || []; for (var i = 0; i < dropTags.length; i++) { var anchor = document.createElement('a'); anchor.classList.add('chip'); anchor.setAttribute('href', '/tags/#' + dropTags[i].replace(' ', '_')); anchor.textContent = dropTags[i]; section.appendChild(anchor); } } section = getSection(document.documentElement, 'Obtained from'); var collectionItems = section ? section.querySelectorAll('.collection-item') : []; for (var i = 0; i < collectionItems.length; i++) { var collectionItem = collectionItems[i]; var obtainType = (collectionItem.textContent || '').trim().toLowerCase(); if (obtainType == 'clothes shop') { var container = document.createElement('div'); container.classList.add('secondary-content'); var drops = metadata['drops'] || []; container.appendChild(createDropLabel(drops.filter(function (x) { return x.endsWith(' G') || x.endsWith(' D'); })[0])); collectionItem.appendChild(container); } } addWardrobeItemCraftingSection('Used to craft', item, CraftingIngredient.prototype.getUsedToCraftPaths.bind(item)); addWardrobeItemCraftingSection('Crafted from', item, CraftingIngredient.prototype.getCraftedFromPaths.bind(item)); } /** * Helper to load all crafting and drop metadata, so we don't have to crawl MyNI pages, * before calling a function. */ function fetchMetadata(callback) { var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://holatuwol.s3-us-west-2.amazonaws.com/crafting.json'); xhr.onload = function () { itemMetadata = JSON.parse(xhr.responseText); callback(); }; xhr.send(null); } /** * Figure out which helper to add to the page. */ function addHelper() { if (pathParts[1] == 'drops') { addDropsHelper(); } else if (pathParts[1] == 'stages') { if ((pathParts.length >= 4) && (pathParts[3] != '')) { addStageHelper(); } else if (pathParts[2] == 'commission') { addCommissionStageHelper(); } else if (pathParts[2] == 'custom') { addCustomStageHelper(); } else if (pathParts[2] == 'stylistsarena') { addArenaStageHelper(); } } else if (pathParts[2] == 'suit') { if (pathParts[3]) { fetchMetadata(addSuitHelper); } else { fetchMetadata(addSuitsHelper); } } else if (pathParts[1] == 'tags') { fetchMetadata(addTagsHelper); } else if (pathParts[1] == 'wardrobe') { if (((pathParts.length == 3) && (pathParts[2] != '')) || ((pathParts.length == 4) && (pathParts[3] == ''))) { fetchMetadata(addWardrobeHelper); } else if (pathParts.length >= 4) { fetchMetadata(addWardrobeItemHelper); } } } addHelper();