diff --git a/calc/package-lock.json b/calc/package-lock.json index 70705e585..d02867d6b 100644 --- a/calc/package-lock.json +++ b/calc/package-lock.json @@ -8454,4 +8454,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/calc/package.json b/calc/package.json index 4120d562c..2f26a63cd 100644 --- a/calc/package.json +++ b/calc/package.json @@ -37,4 +37,4 @@ "pretest": "npm run build", "posttest": "npm run lint" } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index acd1e6d78..1c6cefb45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7206,4 +7206,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index af870e1a0..5208c5e84 100644 --- a/package.json +++ b/package.json @@ -43,4 +43,4 @@ "subPackages": [ "calc" ] -} +} \ No newline at end of file diff --git a/src/index.template.html b/src/index.template.html index b7f3a1e61..d0e6a5cbf 100644 --- a/src/index.template.html +++ b/src/index.template.html @@ -1041,6 +1041,9 @@ +
+ +
@@ -1631,6 +1634,7 @@ +
Created by Honko, maintained by Austin and Kris diff --git a/src/js/sharecalc.js b/src/js/sharecalc.js new file mode 100644 index 000000000..b28b2b2be --- /dev/null +++ b/src/js/sharecalc.js @@ -0,0 +1,417 @@ +/* + Quick Explanation. + This is to share with someone your calculation at a given time. + By clicking Share Calculation at the Import/Export section, + the user gets a link copied to its clipboard. + The user then share this link to another user by external means, + which when opened recreate the calculation. + + I'll call field HTML nodes that contains a data important + needed to recreate the calculation. + Some field might be missing. To add another field to be shared + there are table, or rather maps, that are used to ravel and unravel the data. + Each element of the table have this structure. + [0] => Jquery selector to the HTML node + [1] => The function name to call to extract the data from the HTML node + [2] => Specific to the function name, + for example for the func "val", it's the default value, so when not written it can be + implicitely recovered without writing any data. + + Table wise: + use SHARE_FIELD_TABLE if it's not specific to a pannel, + or if you directly target the field by its ID. + use SHARE_PANNEL_TABLE if it's specific a pannel, + and you have a class or any non-id Jquery selector. + use SHARE_SET_TABLE if it's data that is used by the export/import + because when shared it first insert the calculation protagonists + as a custom set. + + adaptFieldsToGen() function modify just before ravelling the data to + , in idea, to share only what's needed. but to be fair it might be overkill. + +*/ +var SHARE_FIELD_TABLE = [ + ["input:radio[name='defaultLevel']", "find"], + ["input:checkbox[name='terrain']", "find"], + ["input:radio[name='format']", "find"], + ["#beads", "checked"], + ["#tablets", "checked"], + ["#sword", "checked"], + ["#vessel", "checked"], + ["#magicroom", "checked"], + ["#wonderroom", "checked"], + ["#gravity", "checked"], + ["#srL", "checked"], ["#srR", "checked"], + ["#steelsurgeL", "checked"], ["#steelsurgeR", "checked"], + ["#vinelashL", "checked"], ["#vinelashR", "checked"], + ["#wildfireL", "checked"], ["#wildfireR", "checked"], + ["#cannonadeL", "checked"], ["#cannonadeR", "checked"], + ["#volcalithL", "checked"], ["#volcalithR", "checked"], + ["#reflectL", "checked"], ["#reflectR", "checked"], + ["#lightScreenL", "checked"], ["#lightScreenR", "checked"], + ["#protectL", "checked"], ["#protectR", "checked"], + ["#leechSeedL", "checked"], ["#leechSeedR", "checked"], + ["#foresightL", "checked"], ["#foresightR", "checked"], + ["#helpingHandL", "checked"], ["#helpingHandR", "checked"], + ["#tailwindL", "checked"], ["#tailwindR", "checked"], + ["#flowerGiftL", "checked"], ["#flowerGiftR", "checked"], + ["#friendGuardL", "checked"], ["#friendGuardR", "checked"], + ["#auroraVeilL", "checked"], ["#auroraVeilR", "checked"], + ["#batteryL", "checked"], ["#batteryR", "checked"], + ["#powerSpotL", "checked"], ["#powerSpotR", "checked"], + ["#switchingL", "checked"], ["#switchingR", "checked"], + ["#critL1", "checked"], ["#critR1", "checked"], + ["#critL2", "checked"], ["#critR2", "checked"], + ["#critL3", "checked"], ["#critR3", "checked"], + ["#critL4", "checked"], ["#critR4", "checked"], + ["#zL1", "checked"], ["#zR1", "checked"], + ["#zL2", "checked"], ["#zR2", "checked"], + ["#zL3", "checked"], ["#zR3", "checked"], + ["#zL4", "checked"], ["#zR4", "checked"], +]; + +var SHARE_PANNEL_TABLE = [ + [".gender", "keyid", "genders"], + [".max", "checked"], + // bug: gigamax conflicts with current HP because it generates the giga max hp update after the current hp + // effectively doubling the HP it should have + [".current-hp", "val"], + [".status", "indexid", "CALC_STATUS"], + [".at .boost", "val", "0"], + [".df .boost", "val", "0"], + [".sp .boost", "val", "0"], + [".saltcure", "checked"], +]; + +var SHARE_SET_TABLE = [ + [".level", "val", "100"], + [".teraType", "indexid", "typeChart"], + [".nature", "keyidTI", "NATURES_BY_ID"], + [".item", "index", "items"], + [".ability", "index", "abilities"], + [".move1 .select2-offscreen.move-selector", "indexid", "moves"], + [".move2 .select2-offscreen.move-selector", "indexid", "moves"], + [".move3 .select2-offscreen.move-selector", "indexid", "moves"], + [".move4 .select2-offscreen.move-selector", "indexid", "moves"], + //evs // in function of generation + //ivs // -- + //dvs // -- +]; +// GS => Generation Specific +var SHARE_GS_SET_TABLE, SHARE_GS_PANNEL_TABLE, SHARE_GS_FIELD_TABLE; + +var genders = ["Male", "Female", ""]; + +function adaptFieldsToGen() { + SHARE_GS_SET_TABLE = []; + SHARE_GS_FIELD_TABLE = []; + SHARE_GS_PANNEL_TABLE = []; + if (gen == 1) { + SHARE_GS_SET_TABLE = SHARE_GS_SET_TABLE.concat([ + [".sl .dvs", "val", "15"], + ]); + SHARE_GS_PANNEL_TABLE = SHARE_GS_PANNEL_TABLE.concat([ + [".sl .boost", "val", "0"], + ]); + } else { + SHARE_GS_PANNEL_TABLE = SHARE_GS_PANNEL_TABLE.concat([ + [".sa .boost", "val", "0"], + [".sd .boost", "val", "0"], + ]); + } + if (gen == 2) { + SHARE_GS_SET_TABLE = SHARE_GS_SET_TABLE.concat([ + [".sa .dvs", "val", "15"], + [".sd .dvs", "val", "15"], + ]); + SHARE_GS_FIELD_TABLE = SHARE_GS_FIELD_TABLE.concat([ + ["#gscSpikesL", "checked"], + ["#gscSpikesR", "checked"], + ["input:radio[name='gscWeather']", "find"], + ]); + } + if (gen < 3) { + SHARE_GS_SET_TABLE = SHARE_GS_SET_TABLE.concat([ + [".hp .dvs", "val", "15"], + [".at .dvs", "val", "15"], + [".df .dvs", "val", "15"], + [".sp .dvs", "val", "15"], + ]); + } else { + SHARE_GS_FIELD_TABLE = SHARE_GS_FIELD_TABLE.concat([ + ["input:radio[name='spikesL']:checked", "val", "0"], + ["input:radio[name='spikesR']:checked", "val", "0"], + ["input:radio[name='weather']", "find"], + ]); + SHARE_GS_SET_TABLE = SHARE_GS_SET_TABLE.concat([ + [".hp .ivs", "val", "31"], + [".hp .evs", "val", "0"], + [".at .ivs", "val", "31"], + [".at .evs", "val", "0"], + [".df .ivs", "val", "31"], + [".df .evs", "val", "0"], + [".sa .ivs", "val", "31"], + [".sa .evs", "val", "0"], + [".sd .ivs", "val", "31"], + [".sd .evs", "val", "0"], + [".sp .ivs", "val", "31"], + [".sp .evs", "val", "0"], + ]); + } +} + +function exportCalculation() { + adaptFieldsToGen(); + var findChecked = function (query) { + var fullQuery = $(query).toArray(); + for (var i = 0, iLen = fullQuery.length; i < iLen; i++) { + if (fullQuery[i].checked) { + return i; + } + } + return ""; + }; + // the second layer of compaction + // addLoc => additionnal locator, to adapt to pannels + var tableCompaction = function (table, addLoc) { + addLoc = addLoc || ""; + var fieldComposition = ""; + for (var i = 0, iLen = table.length; i < iLen; i++) { + var field = table[i]; + var locator = $(addLoc + field[0]); + var extractor = field[1]; + var data = ""; + if (extractor === "checked") { + data = +locator.prop("checked"); + if (data == 0) { + data = ""; + } + } else if (extractor === "val") { + data = locator.val(); + var defaultVal = field[2]; + if (data == defaultVal) { + data = ""; + } + } else if (extractor === "find") { + data = findChecked(locator); + } else if (extractor === "index") { + var obj = window[field[2]]; + data = obj.indexOf(locator.val()); + } else if (extractor === "keyidTI") { + var obj = window[field[2]]; + var value = window.toID(locator.val()); + data = Object.keys(obj).indexOf(value); + } else if (extractor === "keyid") { + var obj = window[field[2]]; + data = obj.indexOf(locator.val()); + } else if (extractor === "indexid") { + var obj = window[field[2]]; + data = Object.keys(obj).indexOf(locator.val()); + } else if (extractor === "text") { + data = locator.text(); + } + if (!data) { + data = ""; + } + fieldComposition += data + ":"; + } + return fieldComposition; + }; + // directly share the form as the pokemon + var pokeL = $('#p1 .set-selector.select2-offscreen').val().replace(/ \(.*/, ""); + var forme = $('#p1 .forme').val(); + if (pokedex[pokeL].otherFormes && pokedex[pokeL].otherFormes.includes(forme)) pokeL = forme; + var pokeR = $('#p2 .set-selector.select2-offscreen').val().replace(/ \(.*/, ""); + forme = $('#p2 .forme').val(); + if (pokedex[pokeR].otherFormes && pokedex[pokeR].otherFormes.includes(forme)) pokeR = forme; + var fieldComposition = ""; + fieldComposition += Object.keys(pokedex).indexOf( + pokeL) + ":"; + fieldComposition += Object.keys(pokedex).indexOf( + pokeR) + ":"; + fieldComposition += tableCompaction( + SHARE_SET_TABLE.concat(SHARE_GS_SET_TABLE), "#p1 "); + fieldComposition += tableCompaction( + SHARE_SET_TABLE.concat(SHARE_GS_SET_TABLE), "#p2 "); + fieldComposition += tableCompaction( + SHARE_PANNEL_TABLE.concat(SHARE_GS_PANNEL_TABLE), "#p1 "); + fieldComposition += tableCompaction( + SHARE_PANNEL_TABLE.concat(SHARE_GS_PANNEL_TABLE), "#p2 "); + fieldComposition += tableCompaction( + SHARE_FIELD_TABLE.concat(SHARE_GS_FIELD_TABLE)); + // trim the last separator + fieldComposition = fieldComposition.replace(RegExp(":$"), ""); + + /* further compaction alg + if there is 3 or more default values in a row + (care to make the tables in a way that all default value sensitive + row are aligned, so its optimise evenmore) + if the field encounters ;, it will interprets the integer following + as the number of default value in a row. + so the uncompacter will expand it intuitively. + */ + var data = fieldComposition.split(":"); + var compactedFieldComposition = ""; + var skip = 0; + // effectively the first layer of compaction + // which trims 50% of the data + var writeSkipped = function (data, skip) { + if (skip == 1) { + // means "::" + data = data.slice(0, -1) + "!"; + } else if (skip == 2) { + // means ":::" + data = data.slice(0, -1) + "~"; + } else if (skip == 3) { + // means ":::" + data = data.slice(0, -1) + "_"; + } else if (skip > 3) { + data = data.slice(0, -1); + data += ";" + skip + ":"; + } + return data; + }; + for (var i = 0, iLen = data.length; i < iLen; i++) { + var row = data[i]; + if (row === "" && i != 0) { + skip++; + continue; + } + compactedFieldComposition = writeSkipped(compactedFieldComposition, skip); + skip = 0; + compactedFieldComposition += row + ":"; + } + if (skip > 0) { + compactedFieldComposition = writeSkipped(compactedFieldComposition, skip); + } + // trim the last separator + compactedFieldComposition = compactedFieldComposition.replace(RegExp(":$"), ""); + return "&share=" + compactedFieldComposition; +} + +function importCalculation(compactedData) { + /* + uncompact the first layer of compactions + */ + compactedData = compactedData.replaceAll("!", "::"); + compactedData = compactedData.replaceAll("~", ":::"); + compactedData = compactedData.replaceAll("_", "::::"); + compactedData = compactedData.split(";"); + var data = compactedData[0]; + for (var i = 1; i < compactedData.length; i++) { + var row = compactedData[i]; + var skip = row.match(/^[^:]+/)[0]; + row = row.slice(skip.length); + row = ":".repeat(+skip) + row; + data += row; + } + data = data.split(":"); + adaptFieldsToGen(); + // function to uncompact the second layer + var tableUncompaction = function (table, addLoc) { + for (var i = 0, iLen = table.length; i < iLen; i++) { + addLoc = addLoc || ""; + var field = table[i]; + var locator = $(addLoc + field[0]); + var extractor = field[1]; + var field_data = data.splice(0, 1)[0]; + if (extractor === "checked") { + if (+field_data) { + locator.prop("checked", true); + } else { + locator.prop("checked", false); + } + } else if (extractor === "val") { + var defaultVal = field[2]; + if (field_data) { + locator.val(field_data); + } else { + locator.val(defaultVal); + } + } else if (extractor === "find") { + if (field_data !== "") { + locator.eq(field_data).prop("checked", true); + } + } else if (extractor === "index") { + if (field_data) { + var obj = window[field[2]]; + locator.val(obj[field_data]); + } + } else if (extractor === "keyidTI") { + if (field_data) { + var obj = window[field[2]]; + field_data = obj[Object.keys(obj)[field_data]].name; + locator.val(field_data); + } + } else if (extractor === "keyid") { + var obj = window[field[2]]; + field_data = obj[Object.keys(obj)[field_data]]; + locator.val(field_data); + } else if (extractor === "indexid") { + var obj = window[field[2]]; + field_data = Object.keys(obj)[field_data]; + locator.val(field_data); + } else if (extractor === "text") { + locator.text(field_data); + } + } + }; + var pokeL = Object.keys(pokedex)[data.splice(0, 1)[0]]; + var pokeR = Object.keys(pokedex)[data.splice(0, 1)[0]]; + pokeL = pokeL + " (Shared Set L)"; + pokeR = pokeR + " (Shared Set R)"; + $('#p1 input.set-selector').val(pokeL); + $('#p2 input.set-selector').val(pokeR); + tableUncompaction( + SHARE_SET_TABLE.concat(SHARE_GS_SET_TABLE), "#p1 "); + tableUncompaction( + SHARE_SET_TABLE.concat(SHARE_GS_SET_TABLE), "#p2 "); + + // by doing all of this, i don't overwrite any existing set + // with a newly created set it's overall easier to interact with + window.ExportPokemon($('#p1')); + document.getElementsByClassName("import-name-text")[0].value = "Shared Set L"; + $("#import.bs-btn").click(); + window.ExportPokemon($('#p2')); + document.getElementsByClassName("import-name-text")[0].value = "Shared Set R"; + $("#import.bs-btn").click(); + $('#p1 input.set-selector').val(pokeL); + $('#p2 input.set-selector').val(pokeR); + // then actualizing it set all default value to initial + $('input.set-selector').change(); + $('#p1 .set-selector .select2-chosen').text(pokeL); + $('#p2 .set-selector .select2-chosen').text(pokeR); + // just replace all the field by the once shared. + tableUncompaction( + SHARE_PANNEL_TABLE.concat(SHARE_GS_PANNEL_TABLE), "#p1 "); + tableUncompaction( + SHARE_PANNEL_TABLE.concat(SHARE_GS_PANNEL_TABLE), "#p2 "); + tableUncompaction( + SHARE_FIELD_TABLE.concat(SHARE_GS_FIELD_TABLE)); + + //for some reason i had to clean this + document.getElementsByClassName("import-team-text")[0].value = ""; + document.getElementsByClassName("import-name-text")[0].value = "Custom Set"; +} + +$(document).ready(function () { + $('#share-calc').click(function () { + var baseLink = (window.location + "").replace(RegExp("(?<=html).*"), ""); + var gen = "?gen=" + $("input:radio[name='gen']:checked").val(); + var data = exportCalculation(); + navigator.clipboard.writeText(baseLink + gen + data).then(function () { + $('#share-calc').text("Copied to clipboard"); + setTimeout(function () { + $('#share-calc').text("Share Calculation"); + }, 2500); + }); + }); + var params = new URLSearchParams(window.location.search); + var data = params.get('share'); + if (data) { + importCalculation(data); + params.delete('share'); + } + +}); +