Mercurial > hg > batmud > maputils
view www/search.js @ 2484:71e565a46274
Change WebSocket port from TCP 444 to 5222 and also fix the WS connect
failure / error message since we have supported IPv6 for a while.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 18 Feb 2023 17:52:14 +0200 |
parents | 4541c68e1ebf |
children | 81a0f6e6256c |
line wrap: on
line source
// // Frontend map/location search JavaScript functionality // Written by Matti 'ccr' Hamalainen <ccr@tnsp.org> // (C) Copyright 2017-2019 Tecnic Software productions (TNSP) // var fieldPattern, chosenPattern = 0; var msgLog, mapWS, tmpWS, locWS, mapList = []; var mapItemToggles = new Array(); var mapExamples = [ "^^HHHHHHHH+HHHHHH^^^H\n" , "~~~~~~~~~~~~hh^hhhhh\n"+ "~~~~~~~~~~~~~hhh~~~~\n"+ "~~~~~~~~~~~~~h~~~~~~\n"+ "~~~~~~~~~~~~~~~~~~~~\n"+ "~~~~~~~~~~~~~~~~~~~~\n"+ "~~h~~~~~~~~~~~~~~~~~\n"+ "~h^^f~~~~*~~~~~~~~~~\n"+ "~~h^fff~~~~~~~~~~~~~\n"+ "~~~ff^f~~~~~~~~~~~~~\n"+ "~~~~fff~~~~~~h~~~~~~\n"+ "~~~~~~~~~~~h~h~~~~~~\n"+ "~~~~~~~~~~h^^^hh~~~~\n"+ "~~~~~~~~~~h^^^^^hh~~\n" , " lljjjjjjjjhhhhjjjjjjjjjjj \n"+ " llljjjjjjjjhhhhjjjjjjjjjj \n"+ " lllljjjjjjjhhhhjjjjjjjjjj \n"+ " jjllljjjjjjjjhhhhjjjjjjjj \n"+ "jjjllllljjjjj*jjjhjjjjjjjjj\n"+ " jllllllljjjjjjjjjjjjjjjjj \n"+ " jlllllllljjjjjjjjjjjjjjjj \n"+ " jlllllllljjjjjjjjjjjjjjjj \n"+ " jjlljllllljjjjjjjjjjjjjjj \n" , " S \n"+ " SSSSSSSSSS~ \n"+ " SSSSSSSSSSS~~ \n"+ " SSSSsyssSSSSSSS~~ \n"+ " SSSSsyyssssSSSSS~~~ \n"+ " SSSSssssysssSSSSS~~~~ \n"+ " SSSSysshsyyssSSSS~~~~ \n"+ " SSSSSysyhhssssSSSSS~~~~ \n"+ " SSSSSSysshhsssssSSSSS~~~~ \n" , "....|...|\n"+ "----*---+\n"+ "yy.y....|\n" ]; function mapCE(obname, obid) { var mob = document.createElement(obname); if (obid) mob.id = obid; return mob; } function mapClearChildren(obnode) { if (obnode == null || typeof(obnode) == 'undefined') return; while (obnode.firstChild) obnode.removeChild(obnode.firstChild); } function mapAddEventOb(obname, evobj, evtype, evcallback) { if (evobj == null || typeof(evobj) == 'undefined') { console.log("Event object '"+ obname +"' == null."); return; } if (evobj.addEventListener) evobj.addEventListener(evtype, evcallback, false); else if (evobj.attachEvent) evobj.attachEvent("on"+evtype, evcallback); else evobj["on"+evtype] = evcallback; } function mapAddEvent(obname, evtype, evcallback) { mapAddEventOb(obname, document.getElementById(obname), evtype, evcallback); } function mapToggleView(id) { var elem = document.getElementById(id); if (elem) { var val = true; if (id in mapItemToggles) val = mapItemToggles[id]; mapItemToggles[id] = !val; elem.style.display = val ? "block" : "none"; } } function mapCapitalize(str) { return str.substr(0, 1).toUpperCase() + str.substr(1); } function mapResult(msg) { var elem = document.getElementById("results"); if (elem) elem.innerHTML = msg; } function mapValidateJSON(edata, elen1, elen2) { var results; try { results = JSON.parse(edata); } catch (err) { return "Failed to parse JSON: "+ err.message; } if (results && Array.isArray(results)) { for (var i = 0; i < results.length; i++) { var res = results[i]; if (!Array.isArray(res)) return "Invalid data."; if (i == 0 && res.length != elen1) return "Invalid data. First element mismatch."; else if (i > 0 && res.length != elen2) return "Invalid data. Element mismatch."; } return results; } else return "Could not parse result dataset." } function mapGetSelectedMaps() { // Check which maps are enabled, if any var searchList = []; for (var i = 0; i < mapList.length; i++) { var res = mapList[i]; var elem = document.getElementById("map_"+ res[0]); if (elem && elem.checked) searchList.push(res[0]); } return searchList; } function mapUpdateMapCount() { var elem = document.getElementById("mapInfo"); var l = mapGetSelectedMaps(); elem.innerHTML = (l.length == 0) ? "No maps selected!" : "<b>"+ l.length +"</b> map"+ (l.length > 1 ? "s" : "") +" selected."; } function mapSetupWebSocket() { if ("WebSocket" in window) { var mapPort = "5222"; var tmpWS = new WebSocket("wss://tnsp.org:"+ mapPort); if (!tmpWS) { mapResult("WebSocket error: Could not create WebSocket."); return null; } tmpWS.onerror = function(mev) { mapResult("<b>WebSocket error occured!</b>"+ "<p>If this problem persists for more than few hours AND this search HAS worked for you before, "+ "you should check if your Internet provider and firewall allow connections to TCP port "+ mapPort +".</p>" ); console.error("WebSocket error occured: ", mev); }; return tmpWS; } else { mapResult("Your browser does not support WebSockets."); return null; } } function mapHandleError(str) { if (str.substr(0, 6) == "ERROR:") { mapResult("ERROR! "+ str.substr(6).trim()); return false; } else return true; } function mapGetData() { var dataWS = mapSetupWebSocket(); if (!dataWS) return; var mlobj = document.getElementById("mapList"); if (mlobj) mlobj.innerHTML = "Contacting server ..."; dataWS.onopen = function() { if (mlobj) mlobj.innerHTML = "Loading maplist ..."; dataWS.send("GETMAPS"); }; dataWS.onmessage = function(evt) { if (mapHandleError(evt.data)) if (evt.data.substr(0, 5) == "MAPS:" && evt.data.length > 8) { var results = mapValidateJSON(evt.data.substr(5), 3, 3); if (Array.isArray(results) && results.length > 0) { mapList = results; // Create list of map selection buttons var mobj = document.getElementById("mapList"); mapClearChildren(mobj); for (var i = 0; i < results.length; i++) { var res = results[i]; var id = "map_"+ res[0]; // Checkbox input var mbut = mapCE("input", id); mbut.type = "checkbox"; mbut.checked = true; mbut.className = "map"; mapAddEventOb(id, mbut, "change", mapUpdateMapCount); mobj.appendChild(mbut); // Label for button var mlabel = mapCE("label"); mlabel.htmlFor = id; mlabel.textContent = mapCapitalize(res[0]); mobj.appendChild(mlabel); } mapUpdateMapCount(); } else mapResult("ERROR: "+ results); } else mapResult("ERROR: Unknown reply from server."); dataWS.close(); }; } function mapGetLocType(flags) { switch (flags & 0xfffc) { case 0x0004: return "PCITY"; case 0x0008: return "CITY"; case 0x0010: return "SHRINE"; case 0x0020: return "GUILD"; case 0x0040: return "SS"; case 0x0080: return "MONSTER"; case 0x0100: return "TRAINER"; case 0x0200: return "FORT"; default: return null; } } function mapGetLocPrefix(flags) { var str = mapGetLocType(flags); return str != null ? "["+ str + "] " : ""; } function mapGetGMapLink(gx, gy) { return "[ <a class=\"glob\" target=\"_blank\" "+ "href=\"http://jeskko.pupunen.net/gmap2/?x="+ gx +"&y="+ gy +"&zoom=10\">"+ gx +", "+ gy +"</a> ]"; } function mapGetNearby(mx, my, mid) { var qtmpWS = mapSetupWebSocket(); if (!qtmpWS) return; qtmpWS.onopen = function(evt) { qtmpWS.send("LOCNEAR:"+ mx +":"+ my +":"+ 200 +":2:"); }; qtmpWS.onmessage = function(evt) { if (mapHandleError(evt.data)) if (evt.data.substr(0, 7) == "RESULT:" && evt.data.length >= 9) { var results = mapValidateJSON(evt.data.substr(7), 2, 9); if (Array.isArray(results) && results.length > 0 && mid) { var str = ""; // Format results, if any for (var i = 1; i < results.length; i++) { var res = results[i]; var flags = res[5]; var names = res[8]; str += "<div class=\"nearby\" title=\""+ names.join(" | ") +"\">"+ "<a target=\"_blank\" href=\""+ res[0] +".html#loc"+ res[1] +"_"+ res[2] +"\">"+ mapGetLocPrefix(flags) + names[0] +"</a>" + " "+ mapGetGMapLink(res[3], res[4]) + " dist "+ res[7] + "</div>"; } mid.innerHTML += str; } } qtmpWS.close(); }; } function mapDoMapSearch() { // Check the search pattern for some sanity before // submitting to the server .. though we do checks there also. var tmp = fieldPattern.value.trim(); if (tmp == "") { mapResult("Nothing to search for."); return; } if (tmp.length > 25*25) { mapResult("Search pattern too large!"); return; } var searchList = mapGetSelectedMaps(); if (searchList.length == 0 && mapList.length > 0) { mapResult("No maps selected!"); return; } // Are we running an old query? if (mapWS) { mapResult("Old query not finished."); return; } // Open a WebSocket connection .. mapWS = mapSetupWebSocket(); if (!mapWS) return; btnMapSearch.disabled = true; mapWS.onopen = function() { // Web Socket is connected, send data using send() mapWS.send("MAPSEARCH:"+ -1 +":"+ searchList.join(":") +"\n" + fieldPattern.value); }; mapWS.onclose = function() { mapWS = null; btnMapSearch.disabled = false; }; // Register events mapWS.onmessage = function(evt) { if (mapHandleError(evt.data)) if (evt.data.substr(0, 7) == "RESULT:" && evt.data.length >= 9) { var results = mapValidateJSON(evt.data.substr(7), 7, 5); if (Array.isArray(results) && results.length > 0) { var glb = results[0]; var str = "<div class=\"resultHead\">Search pattern size <b>"+ glb[5] +" x "+ glb[6] +"</b>"+ (glb[2] ? " (Centered at <b>"+ glb[3] +", "+ glb[4] +"</b>)" : " (Not centered)") + "</div><div class=\"resultHead\"><b>"+ glb[0] +"</b> match"+ ((glb[0] > 1 || glb[0] == 0) ? "es" : "") +" found, match limit is "+ glb[1] +".</div>"; // First element is the global info if (results.length <= 1) str += "<div class=\"resultHead\">No matches found.</div>"; var mobj = document.getElementById("results"); mobj.innerHTML = str; // Format results, if any for (var i = 1; i < results.length; i++) { var res = results[i]; var mid = mapCE("div"); mid.className = "resultData"; mid.innerHTML = "<span class=\"resultData\">"+ res[1] +", "+ res[2] +" at "+ mapCapitalize(res[0]) +" "+ mapGetGMapLink(res[3], res[4]) + "</span>"; mobj.appendChild(mid); mapGetNearby(res[3], res[4], mid); } } else mapResult("ERROR: "+ results); } else mapResult("ERROR: Unknown reply from server."); mapWS.close(); }; } function mapDoLocSearch() { locPID = -1; // Check the search pattern for some sanity before // submitting to the server .. though we do checks there also. var nearby = false; var tmp = fieldLocPattern.value.trim(); if (tmp == "") { mapResult("Nothing to search for."); return; } if (tmp.length > 30) { mapResult("Search pattern too large!"); return; } if (tmp.substr(0, 1) == "@") { nearby = true; tmp = tmp.substr(1).trim().replace(",", ":") + ":"; } else { // Allow exact matches if (tmp.substr(0, 1) == "^") tmp = tmp.substr(1); else tmp = "*"+ tmp; if (tmp.substr(-1) == "$") tmp = tmp.substr(-1); else tmp = tmp +"*"; } // Are we running an old query? if (locWS) return; // Open a WebSocket connection .. locWS = mapSetupWebSocket(); if (!locWS) return; locWS.onopen = function() { // Web Socket is connected, send data using send() locWS.send((nearby ? "LOCNEAR:" : "LOCSEARCH:") + tmp); }; locWS.onclose = function() { locWS = null; }; // Register events locWS.onmessage = function(evt) { if (mapHandleError(evt.data)) if (evt.data.substr(0, 7) == "RESULT:" && evt.data.length >= 9) { var results = mapValidateJSON(evt.data.substr(7), 2, 9); if (Array.isArray(results) && results.length > 0) { var glb = results[0]; var str = "<div class=\"resultHead\"><b>"+ glb[0] +"</b> match"+ ((glb[0] > 1 || glb[0] == 0) ? "es" : "") +" found, match limit is "+ glb[1] +".</div>"; if (results.length <= 1) str += "<div class=\"resultHead\">No matches found.</div>"; // Format results, if any for (var i = 1; i < results.length; i++) { var res = results[i]; var flags = res[5]; var nname = res[6]; var names = res[8]; str += "<div class=\"resultData\" title=\""+ names.join(" | ") +"\">"+ "<a target=\"_blank\" href=\""+ res[0] +".html#loc"+ res[1] +"_"+ res[2] +"\">"+ mapGetLocPrefix(flags) + names[0] +"</a>" + (nname > 0 ? " ["+ names[nname] +"]" : "") + " at "+ res[1] +", "+ res[2] +" on "+ mapCapitalize(res[0]) + " "+ mapGetGMapLink(res[3], res[4]) + (nearby ? " dist "+ res[7] : "")+ "</div>"; } mapResult(str); } else mapResult("ERROR: "+ results); } else mapResult("ERROR: Unknown reply from server."); locWS.close(); }; } function mapInitSearch() { // Check for disabled Javascript var verr = document.getElementById("noscript"); if (verr) verr.parentNode.removeChild(verr); var mlhelp1 = document.getElementById("help1"); if (mlhelp1) mlhelp1.style.display = "none"; var mlhelp2 = document.getElementById("help2"); if (mlhelp2) mlhelp2.style.display = "none"; if (!("WebSocket" in window)) { mapResult("Your browser does not support WebSockets!"); return; } fieldPattern = document.getElementById("mapPattern"); btnMapSearch = document.getElementById("btnMapSearch"); // Show example search pattern chosenPattern = Math.floor(Math.random() * mapExamples.length); mapAddEvent("btnExample", "click", function () { mapResult("Example search pattern <b>#"+ (chosenPattern + 1) + "/"+ mapExamples.length +"</b> selected. Try clicking 'Search' now."); fieldPattern.value = mapExamples[chosenPattern]; chosenPattern = (chosenPattern + 1) % mapExamples.length; }); // Map search button mapAddEventOb("btnMapSearch", btnMapSearch, "click", mapDoMapSearch); // Clear search button mapAddEvent("btnClear", "click", function () { fieldPattern.value = ""; mapResult("Cleared search pattern and results."); }); mapGetData(); // Reset or clear map list button mapAddEvent("btnMaps", "click", function () { var same = true, first = true; // Get current state of map list for (var i = 0; i < mapList.length; i++) { var elem = document.getElementById("map_"+ mapList[i][0]); if (first) same = elem.checked; else if (elem.checked != same) { same = false; break; } first = false; } // Invert it same = !same; // Set it! Bop it! for (var i = 0; i < mapList.length; i++) { var elem = document.getElementById("map_"+ mapList[i][0]); elem.checked = same; } mapUpdateMapCount(); }); // Listener for location name search fieldLocPattern = document.getElementById("locPattern"); if (fieldLocPattern) { locPID = -1; mapAddEventOb("locPattern", fieldLocPattern, "input", function () { if (locPID != -1) clearTimeout(locPID); locPID = setTimeout(mapDoLocSearch, 500); }); } }