Adds a whole bunch of features to WME, which someday I may get around to documenting properly...
// ==UserScript==
// @name UROverview Plus (URO+)
// @namespace http://greasemonkey.chizzum.com
// @description Adds a whole bunch of features to WME, which someday I may get around to documenting properly...
// @include https://*.waze.com/*editor*
// @exclude https://editor-beta.waze.com/*
// @exclude https://beta.waze.com/*
// @exclude https://www.waze.com/user/*editor/*
// @exclude https://www.waze.com/*/user/*editor/*
// @grant none
// @version 5.0
// ==/UserScript==
/*
=======================================================================================================================
Bug fixes - MUST BE CLEARED BEFORE RELEASE
=======================================================================================================================
=======================================================================================================================
Things to be checked
=======================================================================================================================
*/
const uroRelease =
{
version : "5.0",
date : "20260807",
changes :
[
"SDK migration sufficiently complete to release, though note some things remain broken..."
]
};
const uroEnums =
{
FP_OPTS:
{
filterUneditable: 0,
filterInsideManagedAreas: 1,
excludeMyAreas: 2,
filterLockRanked: 3,
filterFlagged: 4,
filterNewPlace: 5,
filterUpdatedDetails: 6,
filterNewPhoto: 7,
filterMinPURAge: 8,
filterMaxPURAge: 9,
invertPURFilters: 10,
filterHighSeverity: 11,
filterMedSeverity: 12,
filterLowSeverity: 13,
leavePURGeos: 14,
filterCFPhone: 15,
filterCFName: 16,
filterCFEntryExitPoints: 17,
filterCFOpeningHours: 18,
filterCFAliases: 19,
filterCFServices: 20,
filterCFGeometry: 21,
filterCFHouseNumber: 22,
filterCFCategories: 23,
filterCFDescription: 24,
filterOnCFs: 25,
thresholdMinPURDays: 26,
thresholdMaxPURDays: 27,
isLoggedIn: 28,
userRank: 29,
N_OPTS: 30
},
TRTC:
{
UNKNOWN: 0,
WME: 1,
WAZEFEED: 2,
WAZEOTHER: 3,
APP: 4,
PROBLEM_IMPORT: 5,
PARTNER_EDITOR: 6
},
SRTC:
{
UNKNOWN: 0,
EXPIRED: 1,
ACTIVE: 2,
FUTURE: 3
},
DRTC:
{
NONE: 0,
SEG_AB: 1,
SEG_BA: 2,
SEG_BI: 4,
TURN_OUT: 8,
TURN_IN: 16
}
};
const uroCustomURTags = ['[ROADWORKS]','[CONSTRUCTION]','[CLOSURE]','[EVENT]','[NOTE]','[WSLM]','[BOG]','[DIFFICULT]'];
let uroURDupes = [];
let uroFID = -1;
let uroShownFID = -1;
let uroInhibitSave = true;
let uroConfirmIntercepted = false;
let uroMCLayer = null;
let uroVenueLayer = null;
let uro_uFP = [];
let uroPlacesGroupsCollapsed = [];
let uroKnownProblemTypeIDs = [];
let uroKnownProblemTypeNames = [];
let uroPURsToHide = [];
let uroNullOpenLayers = false;
let uroNullURLayer = false;
let uroNullProblemLayer = false;
let uroNullMapViewport = false;
let uroURDialogIsOpen = false;
let uroHoveredURID = null;
let uroSelectedURID = null;
let uroURReclickAttempts = 0;
let uroPendingCommentDataRefresh = false;
let uroWaitingCommentDataRefresh = false;
let uroExpectedCommentCount = null;
let uroCachedLastCommentID = null;
let uroMouseIsDown = false;
let uroHidePopupOnPanelOpen = false;
let uroUserName = null;
let uroMTEMode = false;
let uroDiv = null;
let uroAlerts = null;
let uroControls = null;
let uroCtrlHides = null;
let uroAMList = [];
let uroManagedAreas = [];
let uroIgnoreAreasUserName = undefined;
let uroMousedOverMapComment = null;
let uroMousedOverOtherObjectWithinMapComment = false;
let uroRTCObjs = null;
let uroUnstackedMasterID = null;
let uroStackList = [];
let uroStackType = null;
let uroMainTickHandlerID = null;
let uroMainTickStage = 0;
let uroSettingsApplied = false;
let sdk = null;
let rtcClasses =
{
marker:
{
prefix: 'roadClosure--',
suffix: 'map-marker',
active: 'statusActive--',
finished: 'statusFinished--',
notStarted: 'statusNotStarted--'
},
arrow:
{
generic: 'arrow--',
ab: 'forward',
ba: 'backward',
active: 'active--',
finished: 'inactive--',
notStarted: 'inactive--'
}
};
const uroUtils = // utility functions
{
userCache: [],
escapeHTMLPolicy: null,
FindCachedUserByID: function(id)
{
let idx = null;
for(let i = 0; i < uroUtils.userCache.length; ++i)
{
if(uroUtils.userCache[i].id === id)
{
idx = i;
break;
}
}
return idx;
},
GetUserLevelFromName: function(userName)
{
let level = null;
for(let i = 0; i < uroUtils.userCache.length; ++i)
{
if(uroUtils.userCache[i].userName === userName)
{
level = uroUtils.userCache[i].rank + 1;
break;
}
}
return level;
},
AddUserToCache: function(userObj)
{
let idx = uroUtils.FindCachedUserByID(userObj.id);
if(idx === null)
{
userObj.ts = Date.now();
uroUtils.userCache.push(userObj);
}
else
{
uroUtils.userCache[idx].ts = Date.now();
}
},
CleanUserCache: function()
{
const MAX_AGE = 3600000;
let tsNow = Date.now();
let idx = 0;
while(idx < uroUtils.userCache.length)
{
if((tsNow - uroUtils.userCache[idx].ts) > MAX_AGE)
{
uroUtils.userCache.splice(idx, 1);
}
else
{
++idx;
}
}
},
ExtentContains: function(geo)
{
let retval = false;
let extent = sdk.Map.getMapExtent();
retval = true; //// TBC...
return retval;
},
ModifyHTML: function(htmlIn)
{
if(typeof trustedTypes === "undefined")
{
return htmlIn;
}
else
{
if(this.escapeHTMLPolicy === null)
{
this.escapeHTMLPolicy = trustedTypes.createPolicy("forceInner", {createHTML: (to_escape) => to_escape});
}
return this.escapeHTMLPolicy.createHTML(htmlIn);
}
},
CloneObject: function(objIn)
{
return JSON.parse(JSON.stringify(objIn));
},
GetCBChecked: function(cbID)
{
try
{
return(document.getElementById(cbID).checked);
}
catch(err)
{
uroDBG.AddLog('GetCBChecked() - '+cbID+' not found!');
return null;
}
},
SetCBChecked: function(cbID, state)
{
try
{
document.getElementById(cbID).checked = state;
}
catch(err)
{
uroDBG.AddLog('SetCBChecked() - '+cbID+' not found!');
}
},
GetElmValue: function(elmID)
{
try
{
return(document.getElementById(elmID).value);
}
catch(err)
{
uroDBG.AddLog('GetElmValue() - '+elmID+' not found!');
return null;
}
},
SetOnClick: function(elm,fn)
{
try
{
if(typeof elm == 'object')
{
elm.onclick = fn;
}
else
{
document.getElementById(elm).onclick = fn;
}
}
catch(err)
{
uroDBG.AddLog('SetOnClick() - '+elm+' not found!');
}
},
AddEventListener: function(elm, eventType, eventFn, eventBool)
{
try
{
document.getElementById(elm).addEventListener(eventType, eventFn, eventBool);
}
catch(err)
{
uroDBG.AddLog('AddEventListener() - '+elm+' not found!');
}
},
DateToDays: function(dateToConvert)
{
let dateNow = new Date();
let elapsedSinceEpoch = dateNow.getTime();
let elapsedSinceEvent = elapsedSinceEpoch - dateToConvert;
dateNow.setHours(0);
dateNow.setMinutes(0);
dateNow.setSeconds(0);
dateNow.setMilliseconds(0);
let elapsedSinceMidnight = elapsedSinceEpoch - dateNow.getTime();
dateNow.setHours(24);
let pendingUntilMidnight = elapsedSinceEpoch - dateNow.getTime();
if((elapsedSinceEvent < elapsedSinceMidnight) && (elapsedSinceEvent > pendingUntilMidnight))
{
// event occurred today...
return 0;
}
else if(elapsedSinceEvent < 0)
{
// event occurrs at some point in the future after midnight today, so return a minimum value of -1...
return -1 - Math.floor((pendingUntilMidnight - elapsedSinceEvent) / 86400000);
}
else
{
// event occurred at some point prior to midnight this morning, so return a minimum value of 1...
return 1 + Math.floor((elapsedSinceEvent - elapsedSinceMidnight) / 86400000);
}
},
GetURAge: function(urObj,ageType,getRaw)
{
if(ageType === 0)
{
if((urObj.attributes.driveDate === null)||(urObj.attributes.driveDate === 0)) return -1;
if(getRaw) return urObj.attributes.driveDate;
else return uroUtils.DateToDays(urObj.attributes.driveDate);
}
else if(ageType === 1)
{
if((urObj.attributes.resolvedOn === null)||(urObj.attributes.resolvedOn === 0)) return -1;
if(getRaw) return urObj.attributes.resolvedOn;
else return uroUtils.DateToDays(urObj.attributes.resolvedOn);
}
else
{
return -1;
}
},
GetMCAge: function(mcModData, ageType, getRaw)
{
let retval = {raw: null, days: null};
if(ageType === 0)
{
if((mcModData.createdOn !== null)&&(mcModData.createdOn !== 0))
{
retval.raw = mcModData.createdOn;
retval.days = uroUtils.DateToDays(mcModData.createdOn);
}
}
else if(ageType === 1)
{
if((mcModData.updatedOn !== null)&&(mcModData.updatedOn !== 0))
{
retval.raw = mcModData.updatedOn;
retval.days = uroUtils.DateToDays(mcModData.updatedOn);
}
}
else if(ageType === 2)
{
if((mcModData.endDate !== null)&&(mcModData.endDate !== 0))
{
let tDate = new Date(mcModData.endDate);
retval.raw = tDate;
retval.days = uroUtils.DateToDays(tDate);
}
}
else
{
// do nothing
}
return retval;
},
GetPURAge: function(purObj)
{
if(purObj.attributes.venueUpdateRequests[0].attributes.dateAdded !== null)
{
return uroUtils.DateToDays(purObj.attributes.venueUpdateRequests[0].attributes.dateAdded);
}
else
{
return -1;
}
},
GetCommentAge: function(commentObj)
{
if(commentObj.createdOn === null) return -1;
return uroUtils.DateToDays(commentObj.createdOn);
},
ParseDaysAgo: function(days)
{
if(days === 0) return 'today';
else if(days === 1) return '1 day ago';
else return days+' days ago';
},
ParseDaysToGo: function(days)
{
days = 0 - days;
if(days === 0) return 'today';
else if(days === 1) return 'in 1 day';
else return 'in '+days+' days';
},
GetLocalisedSpeedString: function(thisSpeed)
{
if(thisSpeed !== null)
{
let conversionFactor = 1; // default to metric
let country = null;
if(sdk.DataModel.Countries.getTopCountry() !== null)
{
country = sdk.DataModel.Countries.getTopCountry().name;
}
if(country !== null)
{
// country-specific deviations from the above...
if
(
(country == "United Kingdom") ||
(country == "Jersey") ||
(country == "Guernsey") ||
(country == "United States")
)
{
// countries using MPH
conversionFactor = 1.609;
}
}
let speed = Math.round(thisSpeed / conversionFactor);
let retval = speed;
if(conversionFactor == 1) retval += "KM/H";
else retval += "MPH";
return retval;
}
else return "not set";
},
Clickify: function(desc, suffix)
{
// The terminators array consists of pairs of characters which may be found at either
// end of a URL. The first entry in each pair indicates which character needs to be
// found immediately prior to the URL ('' indicates any character) in order for the
// second entry to be considered as a potential end of URL marker
let terminators = [
['', ' '],
['', ','],
['(', ')'],
['[', ']'],
['', '\r'],
['', '\n']
];
if(desc === null) return '';
if(desc === undefined) return '';
if(desc === '') return '';
if(desc.indexOf("https: one.network") == 0)
{
desc = desc.replaceAll(' ','/');
}
desc = desc.replace(/<\/?[^>]+(>|$)/g, "");
if(desc !== "null")
{
// At the moment we can only clickify links that start with http or https...
if(desc.indexOf('http') != -1)
{
let links = desc.split("http");
desc = '';
let i, j, linkEndPos, descPostLink;
for(i=0; i<links.length; i++)
{
if(links[i].indexOf('://') != -1)
{
let prefix = links[i - 1][links[i - 1].length - 1];
links[i] = "http" + links[i];
linkEndPos = links[i].length + 1;
// work out where the end of the URL is, based on what the character immediately
// preceding the "http" is
for(j=0; j<terminators.length; j++)
{
if(links[i].indexOf(terminators[j][1]) !== -1)
{
if((prefix === terminators[j][0]) || (terminators[j][0] === ''))
{
linkEndPos = Math.min(linkEndPos, links[i].indexOf(terminators[j][1]));
}
}
}
descPostLink = '';
if(linkEndPos < links[i].length)
{
descPostLink = links[i].slice(linkEndPos);
links[i] = links[i].slice(0,linkEndPos);
}
desc += '<a target="_wazeUR" href="'+links[i]+'">'+links[i]+'</a>' + descPostLink;
}
else
{
desc += links[i];
}
}
}
desc = desc.replace(/\n/g,"<br>");
return desc + suffix;
}
else
{
return '';
}
},
GetUserNameAndRank: function(userName)
{
let profileLink = sdk.DataModel.Users.getUserProfileLink({userName: userName});
let userLevel = uroUtils.GetUserLevelFromName(userName);
let retval = '<a href="' + profileLink + '" target="_blank">' + userName + '</a>';
if(userLevel !== null)
{
retval += ' (' + userLevel + ')';
}
return retval;
},
GetDateTimeString: function(ts)
{
let tDateObj = new Date(ts);
let dateLocale;
let timeLocale;
if(uroUtils.GetCBChecked('_cbDateFmtDDMMYY')) dateLocale = 'en-gb';
if(uroUtils.GetCBChecked('_cbDateFmtMMDDYY')) dateLocale = 'en-us';
if(uroUtils.GetCBChecked('_cbDateFmtYYMMDD')) dateLocale = 'ja';
if(uroUtils.GetCBChecked('_cbTimeFmt24H')) timeLocale = 'en-gb';
if(uroUtils.GetCBChecked('_cbTimeFmt12H')) timeLocale = 'en-us';
return tDateObj.toLocaleDateString(dateLocale) + ' ' + tDateObj.toLocaleTimeString(timeLocale);
},
ParsePxString: function(pxString)
{
return parseInt(pxString.split("px")[0]);
},
TypeCast: function(varin)
{
if(varin == "null") return null;
if(typeof varin == "string") return parseInt(varin);
return varin;
},
Truncate: function(val)
{
if(val === null) return val;
if(val < 0) return Math.ceil(val);
return Math.floor(val);
},
KeywordPresent: function(desc, keyword, caseInsensitive)
{
let re;
if(caseInsensitive) re = RegExp(keyword,'i');
else re = RegExp(keyword);
if(desc.search(re) != -1) return true;
else return false;
},
AddStyle: function(ID, css)
{
let head, style;
head = document.getElementsByTagName('head')[0];
if (!head)
{
return;
}
uroUtils.RemoveStyle(ID); // in case it is already there
style = document.createElement('style');
style.innerHTML = uroUtils.ModifyHTML(css);
style.id = ID;
head.appendChild(style);
},
RemoveStyle: function(ID)
{
let style = document.getElementById(ID);
if (style)
{
style.parentNode.removeChild(style);
}
},
GetTS: function(day, month, year, hours, mins)
{
let retval = new Date(0);
retval.setDate(day);
retval.setMonth(month - 1);
retval.setYear(year);
retval.setHours(hours);
retval.setMinutes(mins);
return retval.getTime();
},
ToHex: function(decValue, digits)
{
let modifier = 1;
for(let i=0; i < digits; i++)
{
modifier *= 16;
}
// make sure decValue actually is an integer
decValue = parseInt(decValue);
// adding the modifier ensures we have enough digits, including
// any leading zeros which may be required, for the required output
decValue += modifier;
let retval = decValue.toString(16);
// after converting to hex with the modifier included, we'll have
// as many digits as we need to represent decValue, but also an
// extra leading 1, which now needs to be removed before returning
// the result...
retval = retval.slice(-digits);
retval = retval.toUpperCase();
return retval;
},
GetActiveSidebarTab: function()
{
let retval = null;
let tabIcons = document.querySelectorAll('wz-navigation-item');
for(let i = 0; i < tabIcons.length; ++i)
{
if(tabIcons[i].selected === true)
{
retval = tabIcons[i].attributes['data-for'].value;
break;
}
}
return retval;
},
ConvertWGS84ToMercator: function(point)
{
return point.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
},
ConvertMercatorToWGS84: function(point)
{
return point.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
},
GetSelectedSegmentIDs: function()
{
let sel = sdk.Editing.getSelection();
let retval = [];
if(sel !== null)
{
if(sel.objectType == "segment")
{
retval = sel.ids;
}
}
return retval;
},
IsFilteringEnabled: function (ignoreZoom)
{
let retval = false;
if
(
(uroUtils.GetCBChecked('_cbMasterEnable') === true) &&
(
(ignoreZoom === true) ||
(sdk.Map.getZoomLevel() <= uroUtils.GetElmValue('_inputFilterMinZoomLevel'))
)
)
{
retval = true;
}
return retval;
},
GetUserID: function(filterNameID, tbUserName)
{
if(filterNameID === null)
{
for(let idx in W.model.users.objects)
{
if(W.model.users.objects.hasOwnProperty(idx))
{
if(W.model.users.objects[idx].attributes.userName == tbUserName)
{
filterNameID = W.model.users.objects[idx].attributes.id;
break;
}
}
}
}
return filterNameID;
},
GetLengthString: function(length)
{
let retval = '';
if(length == null)
{
retval = "Default";
}
else if(sdk.Settings.getUserSettings().isImperial == true)
{
retval = length + "ft";
}
else
{
retval = length + "m";
}
return retval;
}
};
const uroTabs = // script tab handling
{
MAX_PER_ROW: 6,
IDS:
{
URS: 0,
MPS: 1,
MCS: 2,
RTCS: 3,
RAS: 4,
PLACES: 5,
////CAMS: 6,
////MISC: 7
MISC: 6
},
FIELDS:
{
TABHEADER: 0,
TABBODY: 1,
LINKID: 2,
TABTITLE: 3,
SHOWFN: 4,
CLICKFN: 5,
STORAGE: 6,
POPULATEFN: 7
},
CtrlTabs: [],
SetStyleDisplay: function(elm,style)
{
try
{
if(typeof elm == 'object')
{
elm.style.display = style;
}
else
{
document.getElementById(elm).style.display = style;
}
}
catch(err)
{
uroDBG.AddLog('SetStyleDisplay() - '+elm+' not found!');
}
},
PopulateMP: function()
{
let tHTML = '';
tHTML += '<input type="checkbox" id="_cbMPFilterOutsideArea">Hide MPs outside my editable area</input><br><br>';
tHTML += '<b>Filter MPs by type:</b><br>';
let i;
for(i=0; i<uroKnownProblemTypeNames.length; i++)
{
tHTML += '<input type="checkbox" id="_cbMPFilter_T'+uroKnownProblemTypeIDs[i]+'">'+uroKnownProblemTypeNames[i]+'</input><br>';
}
tHTML += '<br><input type="checkbox" id="_cbMPFilterUnknownProblem">Unknown problem type</input><br><br>';
tHTML += ' <i>Specially tagged types</i><br>';
tHTML += ' <input type="checkbox" id="_cbFilterElgin">[Elgin]</input><br>';
tHTML += ' <input type="checkbox" id="_cbFilterTrafficCast">[TrafficCast]</input><br>';
tHTML += ' <input type="checkbox" id="_cbFilterTrafficMaster">[TM]</input><br>';
tHTML += ' <input type="checkbox" id="_cbFilterCaltrans">[Caltrans]</input><br>';
tHTML += ' <input type="checkbox" id="_cbFilterTFL">TfL</input><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterReopenedProblem">Reopened Problems</input><br><br>';
tHTML += '<input type="checkbox" id="_cbInvertMPFilter">Invert operation of type filters?</input><br>';
tHTML += '<br><b>Hide closed/solved/unidentified Problems:</b><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterClosed">Closed</input><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterSolved">Solved</input><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterUnidentified">Not identified</input><br><br>';
tHTML += '<input type="checkbox" id="_cbMPClosedUserIDFilter" pairedWith="_cbMPNotClosedUserIDFilter">Closed</input> or ';
tHTML += '<input type="checkbox" id="_cbMPNotClosedUserIDFilter" pairedWith="_cbMPClosedUserIDFilter">Not Closed</input> by user';
tHTML += '<select id="_selectMPUserID" style="width:80%; height:22px;"></select><br>';
tHTML += '<br><b>Hide problems (not turn) by severity:</b><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterLowSeverity">Low</input> ';
tHTML += '<input type="checkbox" id="_cbMPFilterMediumSeverity">Medium</input> ';
tHTML += '<input type="checkbox" id="_cbMPFilterHighSeverity">High</input><br>';
tHTML += '<br><b>Show MPs based on start/end dates:</b><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterStartDate">Start</input> ';
tHTML += '<input type="number" min="1" max="31" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterStartDay"> / ';
tHTML += '<input type="number" min="1" max="12" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterStartMonth"> / ';
tHTML += '<input type="number" min="2010" max="2100" size="4" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterStartYear"><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterEndDate">End</input> ';
tHTML += '<input type="number" min="1" max="31" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterEndDay"> / ';
tHTML += '<input type="number" min="1" max="12" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterEndMonth"> / ';
tHTML += '<input type="number" min="2010" max="2100" size="4" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputMPFilterEndYear"><br>';
tHTML += '<input type="checkbox" id="_cbMPFilterEndDatePassed">End date in the past</input>';
return tHTML;
},
PopulatePlaces: function()
{
let tHTML = '';
tHTML += '<b>Filter PURs by category/status:</b><br>';
tHTML += '<input type="checkbox" id="_cbFilterUneditablePlaceUpdates">Ones I can\'t edit</input><br>';
tHTML += '<input type="checkbox" id="_cbPURFilterInsideManagedAreas">Ones within AM areas</input>';
tHTML += ' (<input type="checkbox" id="_cbPURExcludeUserArea">except my area)</input><br>';
tHTML += '<i>Requires Area Manager layer to be enabled</i><br>';
tHTML += '<input type="checkbox" id="_cbFilterLockRankedPlaceUpdates">Ones with non-zero lockRanks</input><br>';
tHTML += '<input type="checkbox" id="_cbFilterNewPlacePUR">Ones for new places</input><br>';
tHTML += '<input type="checkbox" id="_cbFilterUpdatedDetailsPUR">Ones for updated place details</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFPhone">Phone number</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFName">Name</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFEntryExitPoints">Entry//exit points</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFOpeningHours">Opening hours</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFAliases">Aliases</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFServices">Services</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFGeometry">Geometry</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFHouseNumber">House number</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFCategories">Categories</input><br>';
tHTML += ' <input type="checkbox" id="_cbPURFilterCFDescription">Description</input><br>';
tHTML += '<input type="checkbox" id="_cbFilterNewPhotoPUR">Ones for new photos</input><br>';
tHTML += '<input type="checkbox" id="_cbFilterFlaggedPUR">Ones flagged for attention</input><br>';
tHTML += '<br><input type="checkbox" id="_cbInvertPURFilters">Invert PUR filters</input><br>';
tHTML += '<br><b>Filter PURs by age of submission:</b><br>';
tHTML += '<input type="checkbox" id="_cbEnablePURMinAgeFilter">Hide PURs less than </input>';
tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPURFilterMinDays"> days old<br>';
tHTML += '<input type="checkbox" id="_cbEnablePURMaxAgeFilter">Hide PURs more than </input>';
tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPURFilterMaxDays"> days old<br>';
tHTML += '<hr>';
tHTML += '<br><b>Filter Places by state:</b><br>';
tHTML += 'Hide if last edited<br>';
tHTML += '<input type="checkbox" id="_cbPlaceFilterEditedLessThan"> less than </input>';
tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterPlaceEditMinDays"> days ago<br>';
tHTML += '<input type="checkbox" id="_cbPlaceFilterEditedMoreThan"> more than </input>';
tHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterPlaceEditMaxDays"> days ago<br>';
tHTML += '<br>Hide if locked at level:<br>';
tHTML += ' <input type="checkbox" id="_cbHidePlacesL0">1</input>';
tHTML += ' <input type="checkbox" id="_cbHidePlacesL1">2</input>';
tHTML += ' <input type="checkbox" id="_cbHidePlacesL2">3</input>';
tHTML += ' <input type="checkbox" id="_cbHidePlacesL3">4</input>';
tHTML += ' <input type="checkbox" id="_cbHidePlacesL4">5</input>';
tHTML += ' <input type="checkbox" id="_cbHidePlacesL5">6</input>';
tHTML += ' <input type="checkbox" id="_cbHidePlacesStaff">Staff</input>';
tHTML += '<br> <input type="checkbox" id="_cbHidePlacesAdLocked">AdLocked</input><br>';
tHTML += '<br>Hide by geometry:<br>';
tHTML += ' <input type="checkbox" id="_cbHideAreaPlaces">Areas</input>';
tHTML += ' <input type="checkbox" id="_cbHidePointPlaces">Points</input>';
tHTML += '<br><br><input type="checkbox" id="_cbHidePhotoPlaces" pairedWith="_cbHideNoPhotoPlaces">Hide or </input>';
tHTML += '<input type="checkbox" id="_cbHideNoPhotoPlaces" pairedWith="_cbHidePhotoPlaces">show ones with photos</input><br>';
tHTML += '<input type="checkbox" id="_cbHideLinkedPlaces" pairedWith="_cbHideNoLinkedPlaces">Hide or </input>';
tHTML += '<input type="checkbox" id="_cbHideNoLinkedPlaces" pairedWith="_cbHideLinkedPlaces">show ones with external links</input><br>';
tHTML += '<input type="checkbox" id="_cbHideDescribedPlaces" pairedWith="_cbHideNonDescribedPlaces">Hide or </input>';
tHTML += '<input type="checkbox" id="_cbHideNonDescribedPlaces" pairedWith="_cbHideDescribedPlaces">show ones with descriptive text</input><br>';
tHTML += '<input type="checkbox" id="_cbHideKeywordPlaces" pairedWith="_cbHideNoKeywordPlaces">Hide or </input>';
tHTML += '<input type="checkbox" id="_cbHideNoKeywordPlaces" pairedWith="_cbHideKeywordPlaces">show ones with a name including</input><br>';
tHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textKeywordPlace"><br>';
tHTML += '<br><b>Show Places touched by a specific editor:</b><br>';
tHTML += '<input type="checkbox" id="_cbShowOnlyPlacesCreatedBy">Created by</input> / ';
tHTML += '<input type="checkbox" id="_cbShowOnlyPlacesEditedBy">edited by</input><br>';
tHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textPlacesEditor"><br>';
tHTML += '<select id="_selectPlacesUserID" style="width:80%; height:22px;"></select><br>';
tHTML += '<br><b>Hide Places touched by a specific editor:</b><br>';
tHTML += '<input type="checkbox" id="_cbHideOnlyPlacesCreatedBy">Created by</input> / ';
tHTML += '<input type="checkbox" id="_cbHideOnlyPlacesEditedBy">edited by</input><br>';
tHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textHidePlacesEditor"><br>';
tHTML += '<select id="_selectHidePlacesUserID" style="width:80%; height:22px;"></select><br>';
tHTML += '<br><br><b>Filter Places by category:</b><br>';
tHTML += '<input type="checkbox" id="_cbLeavePURGeos" pairedWith="_cbHidePURsForFilteredPlaces">Keep place visible if linked PUR is hidden, or</input><br>';
tHTML += '<input type="checkbox" id="_cbHidePURsForFilteredPlaces" pairedWith="_cbLeavePURGeos">Hide PURs linked to hidden places</input><br><br>';
let mainCategories = sdk.DataModel.Venues.getVenueMainCategories();
let subCategories = sdk.DataModel.Venues.getVenueSubCategories();
let nCategories = mainCategories.length;
let i;
if(uroPlacesGroupsCollapsed.length != nCategories)
{
for(i = 0; i < nCategories; i++)
{
uroPlacesGroupsCollapsed.push(false);
}
}
for(i = 0; i < nCategories; i++)
{
let parentCategory = mainCategories[i];
if(uroPlacesGroupsCollapsed[i] === true)
{
tHTML += '<i class="fa fa-plus-square-o" style="cursor:pointer;font-size:14px;" id="_uroPlacesGroupState-'+i+'"></i>';
}
else
{
tHTML += '<i class="fa fa-minus-square-o" style="cursor:pointer;font-size:14px;" id="_uroPlacesGroupState-'+i+'"></i>';
}
tHTML += ' <input type="checkbox" id="_cbPlacesFilter-'+parentCategory.id+'"><b>'+parentCategory.localizedName+'</b></input><br>';
tHTML += '<div id="_uroPlacesGroup-'+i+'" style="padding:3px;border-width:2px;border-style:solid;border-color:#FFFFFF">';
for(let ii = 0; ii < subCategories.length; ii++)
{
let subCategory = subCategories[ii];
if(subCategory.categoryId === parentCategory.id)
{
tHTML += ' <input type="checkbox" id="_cbPlacesFilter-'+subCategory.subCategoryId+'">'+subCategory.localizedName+'</input><br>';
}
}
tHTML += '</div>';
}
tHTML += '<input type="checkbox" id="_cbFilterPrivatePlaces"><b>Residential Places</b></input><br>';
tHTML += '<br><input type="checkbox" id="_cbInvertPlacesFilter">Invert Place filters?</input>';
return tHTML;
},
PopulateUR: function()
{
let iHTML = '';
iHTML = '<br>';
iHTML += '<input type="checkbox" id="_cbURFilterOutsideArea">Hide URs outside my editable area</input><br>';
iHTML += '<input type="checkbox" id="_cbURFilterInsideManagedAreas">Hide URs within AM areas</input>';
iHTML += ' (<input type="checkbox" id="_cbURExcludeUserArea">except my area)</input><br>';
iHTML += ' <i>Requires Area Manager layer to be enabled</i><br>';
iHTML += '<input type="checkbox" id="_cbNoFilterForURInURL">Don\'t filter selected UR</input><br><br>';
iHTML += '<input type="checkbox" id="_cbURFilterDupes">Show only duplicate URs</input><br><br>';
iHTML += '<b>Filter by type:</b><br>';
iHTML += '<input type="checkbox" id="_cbFilterWazeAuto">Waze Automatic</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterIncorrectTurn">Incorrect turn</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterIncorrectAddress">Incorrect address</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterIncorrectRoute">Incorrect route</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterMissingRoundabout">Missing roundabout</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterGeneralError">General error</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterTurnNotAllowed">Turn not allowed</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterIncorrectJunction">Incorrect junction</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterMissingBridgeOverpass">Missing bridge overpass</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterWrongDrivingDirection">Wrong driving direction</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterMissingExit">Missing exit</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterMissingRoad">Missing road</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterBlockedRoad">Blocked road</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterMissingLandmark">Missing Landmark</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterSpeedLimits">Missing or Invalid Speed limit</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterUndefined">Undefined</input><br>';
iHTML += ' <i>Specially tagged types</i><br>';
iHTML += ' <input type="checkbox" id="_cbFilterRoadworks">[ROADWORKS]</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterConstruction">[CONSTRUCTION]</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterClosure">[CLOSURE]</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterEvent">[EVENT]</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterNote">[NOTE]</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterBOG">[BOG]</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterDifficult">[DIFFICULT]</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterWSLM">[WSLM]</input><br><br>';
iHTML += '<input type="checkbox" id="_cbInvertURFilter">Invert operation of type filters?</input><br>';
iHTML += '<br><b>Filter by attribute:</b><br>';
iHTML += ' <i>Client OS</i><br>';
iHTML += ' <input type="checkbox" id="_cbFilterOS_IOS">IOS</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterOS_Android">Android</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterOS_AndroidAuto">Android Auto</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterOS_CarPlay">Car Play</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterOS_Unknown">Unknown</input><br>';
iHTML += ' <i>Client Type</i><br>';
iHTML += ' <input type="checkbox" id="_cbFilterSource_MobileClient">App</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterSource_Web">Web</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterSource_MobileWeb">Mobile Web</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterSource_Unknown">Unknown</input><br>';
iHTML += ' <i>EV</i><br>';
iHTML += ' <input type="checkbox" id="_cbFilterIsEV_True">Yes</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterIsEV_False">No</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterIsEV_Unknown">Unknown</input><br>';
iHTML += ' <i>Vehicle Type</i><br>';
iHTML += ' <input type="checkbox" id="_cbFilterVehicle_Private">Car</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterVehicle_Taxi">Taxi</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterVehicle_Motorcycle">Motorbike</input><br>';
iHTML += ' <input type="checkbox" id="_cbFilterVehicle_Unknown">Unknown</input><br>';
iHTML += '<br><input type="checkbox" id="_cbInvertURAttributesFilter">Invert operation of attributes filters?</input><br>';
iHTML += '<br><b>Filter by state:</b><br>';
iHTML += '<input type="checkbox" id="_cbFilterOpenUR">Open</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterClosedUR">Closed</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterSolved">Solved</input><br>';
iHTML += '<input type="checkbox" id="_cbFilterUnidentified">Not identified</input><br><br>';
iHTML += '<br><b>Filter by age of submission:</b><br>';
iHTML += '<input type="checkbox" id="_cbEnableMinAgeFilter">Hide URs less than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMinDays"> days old<br>';
iHTML += '<input type="checkbox" id="_cbEnableMaxAgeFilter">Hide URs more than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMaxDays"> days old<br>';
iHTML += '<br><b>Filter by other details:</b><br>';
iHTML += '<input type="checkbox" id="_cbHideMyFollowed" pairedWith="_cbHideMyUnfollowed">Hide</input> or ';
iHTML += '<input type="checkbox" id="_cbHideMyUnfollowed" pairedWith="_cbHideMyFollowed">show</input> URs I\'m following<br><br>';
iHTML += '<input type="checkbox" id="_cbURDescriptionMustBePresent" pairedWith="_cbURDescriptionMustBeAbsent">Hide</input> or ';
iHTML += '<input type="checkbox" id="_cbURDescriptionMustBeAbsent" pairedWith="_cbURDescriptionMustBePresent">show</input> URs with no description<br>';
iHTML += '<input type="checkbox" id="_cbEnableKeywordMustBePresent">Hide URs not including </input>';
iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textKeywordPresent"><br>';
iHTML += '<input type="checkbox" id="_cbEnableKeywordMustBeAbsent">Hide URs including </input>';
iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textKeywordAbsent"><br>';
iHTML += ' <input type="checkbox" id="_cbCaseInsensitive"><i>Case-insensitive matches?</i></input><br><br>';
iHTML += 'With comments from me?<br>';
iHTML += '<input type="checkbox" id="_cbHideMyComments" pairedWith="_cbHideAnyComments">Yes </input>';
iHTML += '<input type="checkbox" id="_cbHideAnyComments" pairedWith="_cbHideMyComments">No</input><br>';
iHTML += 'If last comment made by me?<br>';
iHTML += '<input type="checkbox" id="_cbHideIfLastCommenter" pairedWith="_cbHideIfNotLastCommenter">Yes </input>';
iHTML += '<input type="checkbox" id="_cbHideIfNotLastCommenter" pairedWith="_cbHideIfLastCommenter">No </input><br>';
iHTML += 'If last comment made by UR reporter?<br>';
iHTML += '<input type="checkbox" id="_cbHideIfReporterLastCommenter" pairedWith="_cbHideIfReporterNotLastCommenter">Yes </input>';
iHTML += '<input type="checkbox" id="_cbHideIfReporterNotLastCommenter" pairedWith="_cbHideIfReporterLastCommenter">No</input><br>';
iHTML += '<input type="checkbox" id="_cbEnableMinCommentsFilter">With less than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMinComments"> comments<br>';
iHTML += '<input type="checkbox" id="_cbEnableMaxCommentsFilter">With more than </input>';
iHTML += '<input type="number" min="0" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMaxComments"> comments<br><br>';
iHTML += '<input type="checkbox" id="_cbEnableCommentAgeFilter2">Last comment less than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterCommentDays2"> days ago<br>';
iHTML += '<input type="checkbox" id="_cbEnableCommentAgeFilter">Last comment more than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterCommentDays"> days ago<br>';
iHTML += ' <input type="checkbox" id="_cbIgnoreOtherEditorComments"><i>Ignore other editor comments?</i></input><br><br>';
iHTML += '<input type="checkbox" id="_cbURUserIDFilter">Without comments from user</input>';
iHTML += '<select id="_selectURUserID" style="width:80%; height:22px;"></select><br>';
iHTML += '<input type="checkbox" id="_cbURResolverIDFilter">Not resolved by user</input>';
iHTML += '<select id="_selectURResolverID" style="width:80%; height:22px;"></select>';
iHTML += '<br><br><input type="checkbox" id="_cbInvertURStateFilter">Invert operation of state/age filters?</input><br>';
iHTML += '<input type="checkbox" id="_cbNoFilterForTaggedURs">Don\'t apply state/age filters to tagged URs</input><br>';
return iHTML;
},
PopulateMC: function()
{
let iHTML = '';
iHTML = '<br>';
iHTML += ' <i>Specially tagged types</i><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterRoadworks">[ROADWORKS]</input><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterConstruction">[CONSTRUCTION]</input><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterClosure">[CLOSURE]</input><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterEvent">[EVENT]</input><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterNote">[NOTE]</input><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterBOG">[BOG]</input><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterDifficult">[DIFFICULT]</input><br>';
iHTML += ' <input type="checkbox" id="_cbMCFilterWSLM">[WSLM]</input><br><br>';
iHTML += '<input type="checkbox" id="_cbInvertMCFilter">Invert operation of type filters?</input><br>';
iHTML += '<hr>';
iHTML += '<br><b>Filter by description/comments/following:</b><br>';
iHTML += '<input type="checkbox" id="_cbMCHideMyFollowed" pairedWith="_cbMCHideMyUnfollowed">Ones I am or </input>';
iHTML += '<input type="checkbox" id="_cbMCHideMyUnfollowed" pairedWith="_cbMCHideMyFollowed">am not following</input><br><br>';
iHTML += '<input type="checkbox" id="_cbMCDescriptionMustBePresent" pairedWith="_cbMCDescriptionMustBeAbsent">Hide</input> or ';
iHTML += '<input type="checkbox" id="_cbMCDescriptionMustBeAbsent" pairedWith="_cbMCDescriptionMustBePresent">show</input> MCs with no description<br>';
iHTML += '<input type="checkbox" id="_cbMCCommentsMustBePresent" pairedWith="_cbMCCommentsMustBeAbsent">Hide</input> or ';
iHTML += '<input type="checkbox" id="_cbMCCommentsMustBeAbsent" pairedWith="_cbMCCommentsMustBePresent">show</input> MCs with no comments<br>';
iHTML += '<input type="checkbox" id="_cbMCExpiryMustBePresent" pairedWith="_cbMCExpiryMustBeAbsent">Hide</input> or ';
iHTML += '<input type="checkbox" id="_cbMCExpiryMustBeAbsent" pairedWith="_cbMCExpiryMustBePresent">show</input> MCs with no expiry date<br>';
iHTML += '<input type="checkbox" id="_cbMCEnableKeywordMustBePresent">Hide MCs not including </input>';
iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textMCKeywordPresent"><br>';
iHTML += '<input type="checkbox" id="_cbMCEnableKeywordMustBeAbsent">Hide MCs including </input>';
iHTML += '<input type="text" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px;" id="_textMCKeywordAbsent"><br>';
iHTML += ' <input type="checkbox" id="_cbMCCaseInsensitive"><i>Case-insensitive matches?</i></input><br>';
iHTML += '<input type="checkbox" id="_cbMCCreatorIDFilter">Show MCs created by user</input>';
iHTML += '<select id="_selectMCCreatorID" style="width:80%; height:22px;"></select><br>';
iHTML += '<br><input type="checkbox" id="_cbHideWRCMCs"><b>Hide Waze_roadclosures MCs</b></input><br>';
iHTML += '<br><b>Hide MCs with lock level:</b><br>';
iHTML += ' <input type="checkbox" id="_cbHideMCRank0">L1</input>';
iHTML += ' <input type="checkbox" id="_cbHideMCRank1">L2</input>';
iHTML += ' <input type="checkbox" id="_cbHideMCRank2">L3</input>';
iHTML += ' <input type="checkbox" id="_cbHideMCRank3">L4</input>';
iHTML += ' <input type="checkbox" id="_cbHideMCRank4">L5</input>';
iHTML += ' <input type="checkbox" id="_cbHideMCRank5">L6</input>';
iHTML += '<hr>';
iHTML += '<input type="checkbox" id="_cbMCEnhancePointMCVisibility">Enhance visibility of point MCs</input>';
return iHTML;
},
PopulateRTC: function()
{
let iHTML = '';
let rtcClassActive = rtcClasses.marker.prefix + ' ' + rtcClasses.marker.active + ' ' + rtcClasses.marker.suffix;
let rtcClassFuture = rtcClasses.marker.prefix + ' ' + rtcClasses.marker.notStarted + ' ' + rtcClasses.marker.suffix;
let rtcClassPast = rtcClasses.marker.prefix + ' ' + rtcClasses.marker.finished + ' ' + rtcClasses.marker.suffix;
iHTML = '<br><b>Hide Road Closures:</b><br>';
// Hidden checkbox to avoid errors when applying settings from previous versions of the script where this was an active control...
iHTML += '<input type="checkbox" id="_cbHideUserRTCs" style="display: none;" />';
iHTML += '<table style="text-align:center;">';
iHTML += '<tr><td/><td><div class="' + rtcClassPast + '" style="margin-left:0px;margin-top:0px;" /></td><td><div class="' + rtcClassActive + '" style="margin-left:0px;margin-top:0px;" /></td><td><div class="' + rtcClassFuture + '" style="margin-left:0px;margin-top:0px;" /></td><td>???</td></tr>';
iHTML += '<tr><td>From WME</td><td><input type="checkbox" id="_cbHideExpiredEditorRTCs" /></td><td><input type="checkbox" id="_cbHideEditorRTCs" /></td><td><input type="checkbox" id="_cbHideFutureEditorRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownEditorRTCs" /></td></tr>';
iHTML += '<tr><td>From WazeFeed</td><td><input type="checkbox" id="_cbHideExpiredWazeFeedRTCs" /></td><td><input type="checkbox" id="_cbHideWazeFeedRTCs" /></td><td><input type="checkbox" id="_cbHideFutureWazeFeedRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownWazeFeedRTCs" /></td></tr>';
iHTML += '<tr><td>From Staff</td><td><input type="checkbox" id="_cbHideExpiredWazeRTCs" /></td><td><input type="checkbox" id="_cbHideWazeRTCs" /></td><td><input type="checkbox" id="_cbHideFutureWazeRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownWazeRTCs" /></td></tr>';
iHTML += '<tr><td>From App</td><td><input type="checkbox" id="_cbHideExpiredAppRTCs" /></td><td><input type="checkbox" id="_cbHideAppRTCs" /></td><td><input type="checkbox" id="_cbHideFutureAppRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownAppRTCs" /></td></tr>';
iHTML += '<tr><td>From Problem Import</td><td><input type="checkbox" id="_cbHideExpiredProbImpRTCs" /></td><td><input type="checkbox" id="_cbHideProbImpRTCs" /></td><td><input type="checkbox" id="_cbHideFutureProbImpRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownProbImpRTCs" /></td></tr>';
iHTML += '<tr><td>From Partner Editor</td><td><input type="checkbox" id="_cbHideExpiredPartEditRTCs" /></td><td><input type="checkbox" id="_cbHidePartEditRTCs" /></td><td><input type="checkbox" id="_cbHideFuturePartEditRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownPartEditRTCs" /></td></tr>';
iHTML += '<tr><td>Unknown source</td><td><input type="checkbox" id="_cbHideExpiredUnknownRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownRTCs" /></td><td><input type="checkbox" id="_cbHideFutureUnknownRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownUnknownRTCs" /></td></tr>';
iHTML += '<tr><td colspan=5 style="text-align:left;">Invert above filters: <input type="checkbox" id="_cbInvertRTCFilters" /></td></tr>';
iHTML += '<tr><td colspan=5><hr style="margin-top:2px;margin-bottom:8px;"></td></tr>';
iHTML += '<tr><td>In Sidepanel</td><td><input type="checkbox" id="_cbHideExpiredSidepanelRTCs" /></td><td><input type="checkbox" id="_cbHideSidepanelRTCs" /></td><td><input type="checkbox" id="_cbHideFutureSidepanelRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownSidepanelRTCs" /></td></tr>';
iHTML += '<tr><td>In Popup</td><td><input type="checkbox" id="_cbHideExpiredPopupRTCs" /></td><td><input type="checkbox" id="_cbHidePopupRTCs" /></td><td><input type="checkbox" id="_cbHideFuturePopupRTCs" /></td><td><input type="checkbox" id="_cbHideUnknownPopupRTCs" /></td></tr>';
iHTML += '</table><br>';
iHTML += '<input type="checkbox" id="_cbShowMTERTCs" pairedWith="_cbHideMTERTCs">Show</input> or ';
iHTML += '<input type="checkbox" id="_cbHideMTERTCs" pairedWith="_cbShowMTERTCs">hide RTCs associated with MTE: </input>';
iHTML += '<select id="_selectRTCMTE" style="width:80%; height:22px;"></select><br>';
iHTML += '<br>';
iHTML += 'Hide if:<br>';
iHTML += '<input type="checkbox" id="_cbEnableRTCDurationFilterLessThan">Duration less than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRTCDurationLessThan"> days<br>';
iHTML += '<input type="checkbox" id="_cbEnableRTCDurationFilterMoreThan">Duration more than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRTCDurationMoreThan"> days<br>';
iHTML += '<br><b>Filter by date/time:</b><br>';
iHTML += '<input type="checkbox" id="_cbRTCFilterShowForTS" pairedWith="_cbRTCFilterHideForTS">Show</input> or ';
iHTML += '<input type="checkbox" id="_cbRTCFilterHideForTS" pairedWith="_cbRTCFilterShowForTS">hide</input> RTCs active at<br>';
iHTML += 'Date: <input type="number" min="1" max="31" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterDay"> / ';
iHTML += '<input type="number" min="1" max="12" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterMonth"> / ';
iHTML += '<input type="number" min="2010" max="2100" size="4" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterYear"><br>';
iHTML += 'Time: <input type="number" min="0" max="23" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterHour">:';
iHTML += '<input type="number" min="0" max="59" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputRTCFilterMin">';
return iHTML;
},
PopulateRA: function()
{
let iHTML = '';
iHTML = '<br><b>Filter Restricted Areas:</b><br>';
iHTML += '<input type="checkbox" id="_cbShowSpecificRA">Show a specific area: </input>';
iHTML += '<select id="_selectRA" style="width:80%; height:22px;"></select><br><br>';
iHTML += '<input type="checkbox" id="_cbRAEditorIDFilter">Show areas edited by user: </input>';
iHTML += '<select id="_selectRAEditorID" style="width:80%; height:22px;"></select><br><br>';
iHTML += 'Hide if:<br>';
iHTML += '<input type="checkbox" id="_cbEnableRAAgeFilterLessThan">Last modified less than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRAAgeLessThan"> days ago<br>';
iHTML += '<input type="checkbox" id="_cbEnableRAAgeFilterMoreThan">Last modified more than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterRAAgeMoreThan"> days ago<br>';
return iHTML;
},
PopulateMisc: function()
{
let iHTML = '';
iHTML += '<br><b><input type="checkbox" id="_cbHideSegmentsWhenRoadsHidden" />Hide segment layer when road layer is hidden</b><br>';
iHTML += '<br><b><input type="checkbox" id="_cbKillInertialPanning" />Stop inertial panning when mouse moves out of map area</b><br>';
iHTML += '<br><br><b><input type="checkbox" id="_cbCommentCount" />Show comment count on UR markers</b><br>';
iHTML += '<br><br><b><input type="checkbox" id="_cbAutoApplyClonedClosure" />Auto-apply cloned closures</b><br>';
iHTML += '<b><input type="checkbox" id="_cbAutoScrollClosureList" />Auto-scroll to end of closures</b><br>';
iHTML += '<br><br><b>Disable filtering above zoom level </b>';
iHTML += '<input type="number" min="12" max="22" value="22" size="2" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterMinZoomLevel" /><br>';
iHTML += '<br><br><b>Marker Unstacking:</b><br>';
iHTML += 'Distance threshold: <input type="number" min="1" max="30" value="15" size="2" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputUnstackSensitivity" /><br>';
iHTML += 'Disable below zoom: <input type="number" min="12" max="22" value="15" size="2" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputUnstackZoomLevel" /><br>';
iHTML += '<br><br><b>Popup mouse behaviour:</b><br>';
iHTML += 'Mouseover show delay <input type="number" min="1" max="10" value="2" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPopupEntryTimeout" /> *100ms<br>';
iHTML += 'Mouseout hide delay <input type="number" min="1" max="10" value="2" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPopupExitTimeout" /> *100ms<br>';
iHTML += 'Auto-hide after <input type="number" min="0" max="10" value="0" size="2" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputPopupAutoHideTimeout" /> seconds<br>';
iHTML += '<br><br><b>Disable clustering for:</b><br>';
iHTML += '<input type="checkbox" id="_cbInhibitURClusters" />URs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitMPClusters" />MPs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitPUClusters" />PURs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitESClusters" />ESs<br>';
iHTML += '<br><br><b>Disable popup for:</b><br>';
iHTML += '<input type="checkbox" id="_cbInhibitURPopup" />URs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitMPPopup" />MPs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitSegPopup" />Segments<br>';
iHTML += ' <input type="checkbox" id="_cbInhibitSegGenericPopup" />Speed limit info<br>';
iHTML += '<input type="checkbox" id="_cbInhibitLandmarkPopup" />Places<br>';
iHTML += '<input type="checkbox" id="_cbInhibitPUPopup" />Place Updates<br>';
iHTML += '<input type="checkbox" id="_cbInhibitMapCommentPopup" />Map Comments<br>';
iHTML += '<input type="checkbox" id="_cbInhibitNodesPopup" />Junction Nodes<br>';
iHTML += '<br><br><b>Date/Time formatting for popups:</b><br>';
iHTML += '<input type="checkbox" id="_cbDateFmtDDMMYY" pairedWith="_cbDateFmtMMDDYY,_cbDateFmtYYMMDD" checked />day/month/year<br>';
iHTML += '<input type="checkbox" id="_cbDateFmtMMDDYY" pairedWith="_cbDateFmtDDMMYY,_cbDateFmtYYMMDD" />month/day/year<br>';
iHTML += '<input type="checkbox" id="_cbDateFmtYYMMDD" pairedWith="_cbDateFmtMMDDYY,_cbDateFmtDDMMYY" />year/month/day<br><br>';
iHTML += '<input type="checkbox" id="_cbTimeFmt24H" pairedWith="_cbTimeFmt12H" checked />24 hour<br>';
iHTML += '<input type="checkbox" id="_cbTimeFmt12H" pairedWith="_cbTimeFmt24H" />12 hour<br><br>';
iHTML += '<i>Unticked uses browser default setting</i>';
iHTML += '<br><br><b><input type="checkbox" id="_cbWhiteBackground" />Use custom background colour</b><br>';
iHTML += 'R:<input type="number" min="0" max="255" value="255" size="3" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputCustomBackgroundRed" />';
iHTML += 'G:<input type="number" min="0" max="255" value="255" size="3" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputCustomBackgroundGreen" />';
iHTML += 'B:<input type="number" min="0" max="255" value="255" size="3" style="width:50px;;line-height:14px;height:22px;margin-bottom:4px;" id="_inputCustomBackgroundBlue" /><br>';
iHTML += '<br><br><b>Replace "Next ..." button with "Done" for:</b><br>';
iHTML += '<input type="checkbox" id="_cbInhibitNURButton" />URs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitNMPButton" />MPs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitNPURButton" />PURs<br>';
iHTML += '<br><br><b>Disable on-click recentering for:</b><br>';
iHTML += '<input type="checkbox" id="_cbInhibitURCentering" />URs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitMPCentering" />MPs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitPURCentering" />PURs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitPPURCentering" />PPURs<br>';
iHTML += '<input type="checkbox" id="_cbInhibitRPURCentering" />RPURs<br>';
iHTML += '<br><br><b><input type="checkbox" id="_cbHideAMLayer" />Hide Area Manager polygons</b><br>';
iHTML += '<b><input type="checkbox" id="_cbMoveAMList" />Show AMs in topbar when AM layer is active</b><br>';
iHTML += 'Hide areas for AM levels:<br>';
iHTML += ' <input type="checkbox" id="_cbHideAMA-L1">L1</input>';
iHTML += ' <input type="checkbox" id="_cbHideAMA-L2">L2</input>';
iHTML += ' <input type="checkbox" id="_cbHideAMA-L3">L3</input>';
iHTML += ' <input type="checkbox" id="_cbHideAMA-L4">L4</input>';
iHTML += ' <input type="checkbox" id="_cbHideAMA-L5">L5</input>';
iHTML += ' <input type="checkbox" id="_cbHideAMA-L6">L6</input>';
iHTML += ' <input type="checkbox" id="_cbHideAMA-L7">L7</input><br>';
iHTML += 'Hide areas by age:<br>';
iHTML += ' <input type="checkbox" id="_cbEnableAMAMinAgeFilter">Less than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterAMA-MinDays"> days old<br>';
iHTML += ' <input type="checkbox" id="_cbEnableAMAMaxAgeFilter">More than </input>';
iHTML += '<input type="number" min="1" size="3" style="width:50px;line-height:14px;height:22px;margin-bottom:4px;" id="_inputFilterAMA-MaxDays"> days old<br>';
iHTML += '<br><b><input type="checkbox" id="_cbDisablePlacesFiltering" />Disable Places filtering</b><br>';
iHTML += '<br><br><b>Settings backup/restore/reset:</b><br>';
iHTML += '<input type="button" id="_btnSettingsToText" value="Backup" /> ';
iHTML += '<input type="button" id="_btnTextToSettings" value="Restore" /> | ';
iHTML += '<input type="button" id="_btnResetSettings" value="Reset" /><br><br>';
iHTML += '<textarea id="_txtSettings" value=""></textarea><br>';
iHTML += '<input type="button" id="_btnClearSettingsText" value="Clear" /><br>';
/*
iHTML += '<br><br><b>Debug:</b><br>';
iHTML += '<input type="button" id="_btnDebugToScreen" value="Show debug data" />';
*/
return iHTML;
},
ActiveTab: function(_id)
{
let e = document.getElementById(_id);
e.style.backgroundColor = "greenyellow";
e.style.borderTop = "1px solid";
e.style.borderLeft = "1px solid";
e.style.borderRight = "1px solid";
e.style.borderBottom = "0px solid";
},
InactiveTab: function(_id)
{
let e = document.getElementById(_id);
e.style.backgroundColor = "gainsboro";
e.style.borderTop = "0px solid";
e.style.borderLeft = "0px solid";
e.style.borderRight = "0px solid";
e.style.borderBottom = "1px solid";
},
InactiveAllTabs: function()
{
for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
{
uroTabs.InactiveTab(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABHEADER]);
uroTabs.SetStyleDisplay(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY], 'none');
}
},
ShowTab: function(tabID)
{
uroTabs.InactiveAllTabs();
uroTabs.ActiveTab(uroTabs.CtrlTabs[tabID][uroTabs.FIELDS.TABHEADER]);
uroTabs.SetStyleDisplay(uroTabs.CtrlTabs[tabID][uroTabs.FIELDS.TABBODY], 'block');
return false;
},
ShowURs: function()
{
uroTabs.ShowTab(uroTabs.IDS.URS);
return false;
},
ShowMPs: function()
{
uroTabs.ShowTab(uroTabs.IDS.MPS);
return false;
},
ShowMCs: function()
{
uroTabs.ShowTab(uroTabs.IDS.MCS);
return false;
},
ShowPlaces: function()
{
uroTabs.ShowTab(uroTabs.IDS.PLACES);
for(let idx=0;idx<uroPlacesGroupsCollapsed.length;idx++)
{
uroPlacesGroupCEHandler(idx);
}
return false;
},
ShowCams: function()
{
uroTabs.ShowTab(uroTabs.IDS.CAMS);
return false;
},
ShowMisc: function()
{
uroTabs.ShowTab(uroTabs.IDS.MISC);
return false;
},
ShowRTCs: function()
{
uroTabs.ShowTab(uroTabs.IDS.RTCS);
return false;
},
ShowRAs: function()
{
uroTabs.ShowTab(uroTabs.IDS.RAS);
return false;
},
ClickURs: function()
{
uroFilterURs();
},
ClickMPs: function()
{
uroFilterProblems();
},
ClickMCs: function()
{
uroFilterMapComments();
uroLayers.MCLayerChanged();
},
ClickPlaces: function()
{
uroFilterPlaces();
},
ClickMisc: function()
{
uroFilterItems();
uroUITweaks.ChangeMapBGColour();
uroUITweaks.HideAMLayer();
uroUITweaks.HideSegments();
uroUITweaks.ChangeClustering();
},
ClickRTCs: function()
{
uroRTCs.Filter();
uroClosureListHandler();
},
ClickRAs: function()
{
uroFilterRAs();
},
CreateTabHeaders: function()
{
uroTabs.CtrlTabs =
[
['_tabURs', null, '_linkURs', 'URs', uroTabs.ShowURs, uroTabs.ClickURs, 'UROverviewUROptions', uroTabs.PopulateUR],
['_tabMPs', null, '_linkMPs', 'MPs', uroTabs.ShowMPs, uroTabs.ClickMPs, 'UROverviewMPOptions', uroTabs.PopulateMP],
['_tabMCs', null, '_linkMCs', 'MCs', uroTabs.ShowMCs, uroTabs.ClickMCs, 'UROverviewMCOptions', uroTabs.PopulateMC],
['_tabRTCs', null, '_linkRTCs', 'RTCs', uroTabs.ShowRTCs, uroTabs.ClickRTCs, 'UROverviewRTCOptions', uroTabs.PopulateRTC],
['_tabRAs', null, '_linkRAs', 'RAs', uroTabs.ShowRAs, uroTabs.ClickRAs, 'UROverviewRAOptions', uroTabs.PopulateRA],
['_tabPlaces', null, '_linkPlaces', 'Places', uroTabs.ShowPlaces, uroTabs.ClickPlaces, 'UROverviewPlacesOptions', uroTabs.PopulatePlaces],
['_tabMisc', null, '_linkMisc', 'Misc', uroTabs.ShowMisc, uroTabs.ClickMisc, 'UROverviewMiscOptions', uroTabs.PopulateMisc]
];
let i;
let tabsTotal = uroTabs.CtrlTabs.length;
let tabsPerRow = Math.ceil(tabsTotal / Math.ceil(tabsTotal / uroTabs.MAX_PER_ROW));
let tabCount = 0;
let tabbyHTML = '';
for(i = 0; i < tabsTotal; ++i)
{
if(tabCount == 0)
{
tabbyHTML += '<table border=0 width="100%"><tr>';
}
tabbyHTML += '<td valign="center" align="center" id="'+uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABHEADER]+'">';
tabbyHTML += '<a href="#" id="'+uroTabs.CtrlTabs[i][uroTabs.FIELDS.LINKID]+'" style="text-decoration:none;font-size:12px">'+uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABTITLE]+'</a></td>';
if(((++tabCount == tabsPerRow) && (i < (tabsTotal - 1))) || (i == (tabsTotal - 1)))
{
tabbyHTML += '</tr></table>';
tabCount = 0;
}
}
return tabbyHTML;
},
CreateTabBodies: function()
{
for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
{
uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY] = document.createElement('div');
}
},
AddToDOM: function()
{
let i;
for(i = 0; i < uroTabs.CtrlTabs.length; ++i)
{
if(uroTabs.CtrlTabs[i][uroTabs.FIELDS.POPULATEFN] != null)
{
uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY].innerHTML = uroTabs.CtrlTabs[i][uroTabs.FIELDS.POPULATEFN]();
}
document.getElementById('uroControlsContainer').appendChild(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY]);
uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY].onclick = uroTabs.CtrlTabs[i][uroTabs.FIELDS.CLICKFN];
}
}
};
const uroDBG = // debug output handling
{
// true enables debug output during script startup
showDebugOutput: true,
// true keeps debug output enabled after script startup
persistentDebugOutput: false,
// true enables performance monitoring debug output
performanceMonitoringOutput: false,
recentDebug: [],
Add: function(debugtext)
{
let ts = Math.round(performance.now());
if(uroDBG.recentDebug.length == 100)
{
uroDBG.recentDebug.shift();
}
uroDBG.recentDebug.push(ts+': '+debugtext);
console.debug('URO+DBG '+ts+':'+debugtext);
},
Dump: function()
{
if(uroDBG.recentDebug.length > 0)
{
document.getElementById('WazeMap').innerHTML = uroUtils.ModifyHTML('<textarea id="uroDbgOutput" style="width:100%;height:100%">');
let dbgOutput = '';
for(let i = 0; i < uroDBG.recentDebug.length; i++)
{
dbgOutput += uroDBG.recentDebug[i]+'\n';
}
document.getElementById('uroDbgOutput').textContent = dbgOutput;
}
},
Toggle: function()
{
uroDBG.showDebugOutput = !uroDBG.showDebugOutput;
let dbgMode = "none";
if(uroDBG.showDebugOutput)
{
dbgMode = "inline";
}
document.getElementById('_uroDebugMode').style.display = dbgMode;
},
PerfMon: function(source, ts)
{
if(uroDBG.performanceMonitoringOutput === true)
{
console.log(source+': '+(performance.now() - ts));
}
},
AddLog: function(logtext)
{
if(uroDBG.showDebugOutput) console.log('URO+: '+Date()+' '+logtext);
}
};
const uroAFN = // area friendly name functions
{
friendlyNames: [],
hoverTime: -1,
hoverObj: null,
overlayShown: false,
editHovered: false,
editBox: null,
friendlyAreaNames: null,
AFNObject: function(fName, area, server)
{
this.fName = fName;
this.area = area;
this.server = server;
},
GetServer: function()
{
let retval = null;
let pl = sdk.Map.getPermalink();
if(pl.indexOf('env=row' !== -1))
{
retval = 'row';
}
else if(pl.indexOf('env=il' !== -1))
{
retval = 'il';
}
else if(pl.indexOf('env=usa' !== -1))
{
retval = 'usa';
}
return retval;
},
UpdateName: function(name, server, area)
{
let foundExisting = false;
for(let i = 0; i < uroAFN.friendlyNames.length; i++)
{
if((uroAFN.friendlyNames[i].server == server) && (uroAFN.friendlyNames[i].area == area))
{
if(name === "")
{
this.friendlyNames.splice(i, 1);
foundExisting = true;
}
else
{
uroAFN.friendlyNames[i].fName = name;
foundExisting = true;
}
}
}
if((foundExisting === false) && (name !== ""))
{
uroAFN.friendlyNames.push(new uroAFN.AFNObject(name, area, server));
}
uroAFN.ReplaceAreaNames(true);
},
AreaNameHover: function()
{
if((uroAFN.hoverObj === null) || (uroAFN.hoverObj != this))
{
uroAFN.hoverTime = 0;
}
uroAFN.hoverObj = this;
},
AreaNameUnHover: function()
{
if(uroAFN.editHovered === true)
{
return false;
}
if(uroAFN.overlayShown)
{
uroAFN.hoverObj.removeChild(uroAFN.editBox);
}
uroAFN.hoverObj = null;
uroAFN.hoverTime = -1;
uroAFN.overlayShown = false;
},
EditHover: function()
{
uroAFN.editHovered = true;
uroUtils.AddEventListener('uroANEditBox', 'mouseout', uroAFN.EditUnHover, false);
uroUtils.AddEventListener('uroANEditBox', 'click', uroAFN.EditClick, false);
},
EditUnHover: function()
{
let newName = document.getElementById('_textAreaName').value;
// sanitise name to avoid conflicts with config storage delimiters...
newName = newName.replace(',','');
newName = newName.replace(':','');
let server = uroAFN.GetServer();
let area = uroAFN.GetAreaArea(uroAFN.hoverObj.parentNode.children[1].innerText);
uroAFN.hoverObj.removeChild(uroAFN.editBox);
uroAFN.overlayShown = false;
uroAFN.editHovered = false;
uroAFN.UpdateName(newName, server, area);
},
EditClick: function(e)
{
// this traps the click to prevent it falling through to the underlying area name element and
// potentially causing the map view to be relocated to that area...
e.stopPropagation();
},
GetAreaArea: function(area)
{
area = parseFloat(area.split(' ')[0]);
return area;
},
OverlaySetup: function()
{
uroAFN.overlayShown = true;
uroAFN.editBox = document.createElement('div');
uroAFN.editBox.id = "uroANEditBox";
uroAFN.editBox.style.position = "absolute";
uroAFN.editBox.style.top = '0px';
uroAFN.editBox.style.left = '0px';
uroAFN.editBox.style.width = "99%";
uroAFN.hoverObj.appendChild(uroAFN.editBox);
uroAFN.editBox.onmouseover = uroAFN.EditHover();
let existingName = uroAFN.hoverObj.innerHTML;
let italicTagPos = existingName.indexOf(' <i>');
if(italicTagPos == -1)
{
existingName = "";
}
else
{
existingName = existingName.substring(0,italicTagPos);
}
uroAFN.editBox.innerHTML = uroUtils.ModifyHTML('<input type="text" style="font-size:14px; line-height:16px; height:22px; width:100%" id="_textAreaName" value="'+existingName+'">');
},
ReplaceAreaNames: function(replaceAfterNameChange)
{
if(document.getElementById('sidepanel-areas') === null)
{
return;
}
if(document.getElementById('sidepanel-areas').getElementsByClassName('list-item-card-layout').length === 0)
{
return;
}
if(replaceAfterNameChange === false)
{
if(document.getElementById('sidepanel-areas').getElementsByClassName('list-item-card-layout')[0].id == "friendlyNamed")
{
return;
}
}
let panelRootObj = document.getElementById('sidepanel-areas').getElementsByClassName('list-item-card')[0].parentElement;
if(panelRootObj === undefined)
{
// we get here if the user doesn't have any areas defined...
return;
}
let areaNameObjs = panelRootObj.getElementsByClassName('list-item-card-info');
if(areaNameObjs.length === 0)
{
return;
}
let localisedManagedArea = I18n.lookup("user.areas.managed_area");
for(let loop=0; loop < areaNameObjs.length; loop++)
{
if(areaNameObjs[loop].children.length === 2)
{
let title = areaNameObjs[loop].children[0].innerText;
if(title.indexOf(localisedManagedArea) > -1)
{
let area = uroAFN.GetAreaArea(areaNameObjs[loop].children[1].innerText);
areaNameObjs[loop].children[0].innerHTML = uroUtils.ModifyHTML(localisedManagedArea);
for(let fnIdx=0; fnIdx < uroAFN.friendlyNames.length; fnIdx++)
{
let fnObj = uroAFN.friendlyNames[fnIdx];
if((fnObj.area == area) && (fnObj.server == uroAFN.GetServer()))
{
areaNameObjs[loop].children[0].innerHTML = uroUtils.ModifyHTML(fnObj.fName +' <i>('+localisedManagedArea+')</i>');
break;
}
}
let titleObj = areaNameObjs[loop].getElementsByClassName('list-item-card-title')[0];
titleObj.addEventListener("mouseover", uroAFN.AreaNameHover, false);
titleObj.addEventListener("mouseout", uroAFN.AreaNameUnHover, false);
titleObj.style.cursor = "text";
}
}
}
document.getElementById('sidepanel-areas').getElementsByClassName('list-item-card-layout')[0].id = "friendlyNamed";
},
ApplyNames: function()
{
let objects = localStorage.UROverviewFriendlyAreaNames.split(':');
uroAFN.friendlyNames = [];
for(let objIdx=0;objIdx<objects.length;objIdx++)
{
let fields = objects[objIdx].split(',');
uroAFN.friendlyNames.push(new uroAFN.AFNObject(fields[0],parseFloat(fields[1]),fields[2]));
}
uroAFN.ReplaceAreaNames(true);
}
};
const uroMarkers = // marker-related function
{
elm : null,
obj : null,
id : null,
type : null,
lastOver : null,
mouseX : null,
mouseY : null,
mouseButtons : null,
clientX : null,
clientY : null,
armHover : false,
pEntryTimeout : null,
inhibitSetCenter : false,
clickedOnCenter : null,
clickedOnID : null,
EntryTimeout: function()
{
uroMarkers.armHover = false;
if(uroMarkers.lastOver !== null)
{
if((uroMarkers.type == uroLayersFT.ID.UR) || (uroMarkers.type == uroLayersFT.ID.MP))
{
if(uroMarkers.type == uroLayersFT.ID.UR) uroHoveredURID = uroMarkers.id;
}
uroDBG.AddLog('hover over marker (Type ' + uroMarkers.type + ' / ID ' + uroMarkers.id + ')');
uroPopup.Generate();
}
},
MouseMove: function(e)
{
uroMarkers.buttons = e.buttons;
uroMarkers.mouseX = e.pageX - document.getElementById('map').getBoundingClientRect().left;
uroMarkers.mouseY = e.pageY - document.getElementById('map').getBoundingClientRect().top;
uroMarkers.clientX = e.clientX;
uroMarkers.clientY = e.clientY;
if(uroMarkers.armHover === true)
{
if(uroMarkers.pEntryTimeout !== null)
{
window.clearTimeout(uroMarkers.pEntryTimeout);
}
let eto = uroUtils.GetElmValue('_inputPopupEntryTimeout') * 100;
if(uroStackType !== null)
{
eto = 1;
}
uroMarkers.pEntryTimeout = window.setTimeout(uroMarkers.EntryTimeout, eto);
}
},
TranslateType: function(layer)
{
let retval = null;
for(let i = 0; i < uroLayers.layers.length; ++i)
{
if(uroLayers.layers[i].name == layer)
{
retval = uroLayers.layers[i].ft;
break;
}
}
return retval;
},
GetObject: function(id, layer)
{
let retval = null;
for(let i = 0; i < uroLayers.layers.length; ++i)
{
if(uroLayers.layers[i].name == layer)
{
let ul = uroLayers.layers[i];
////if(ul.l.features === undefined)
{
let dm = ul.dm;
let gbi = ul.gbi;
if((dm !== null) && (gbi !== null))
{
retval = sdk.DataModel[dm].getById({[gbi]: id});
break;
}
}
/*
else
{
for(let j = 0; j < ul.l.features.length; ++j)
{
if(ul.l.features[j].attributes.wazeFeature.id === id)
{
retval = document.querySelector('#' + ul.l.features[j].geometry.id);
break;
}
}
}
*/
}
}
return retval;
},
MouseOver: function(e)
{
let obj = uroMarkers.GetObject(e.featureId, e.layerName);
if(obj !== null)
{
// single marker...
uroMarkers.elm = sdk.Map.getFeatureDomElement({featureId: e.featureId, layerName: e.layerName});
uroMarkers.obj = obj;
uroMarkers.id = obj.id;
uroMarkers.type = uroMarkers.TranslateType(e.layerName);
uroMarkers.lastOver = obj.id;
uroMarkers.armHover = true;
uroMarkers.AddMarkerEventHandler();
}
},
RestackAfterMouseOut: function()
{
if(uroMarkers.lastOver === null)
{
uroRestackMarkers();
uroFilterURs();
uroFilterProblems();
uroFilterPlaces();
}
},
MouseOut: function()
{
uroMarkers.lastOver = null;
if(uroMarkers.type !== null)
{
uroDBG.AddLog('hover off '+uroMarkers.type+' ID '+uroMarkers.id);
uroHoveredURID = null;
uroFID = -1;
if(uroStackType !== null)
{
window.setTimeout(uroMarkers.RestackAfterMouseOut, 100);
}
if(uroPopup.timer == -1)
{
uroPopup.timer = uroUtils.GetElmValue('_inputPopupExitTimeout');
}
}
else
{
uroDBG.AddLog('hover off unknown object...');
}
},
MouseDown: function()
{
// Do this stuff in the mousedown event rather than the click event so we fire before any of the native
// click events - we need to ensure this happens for inhibiting marker centering, as we need to capture
// the markerType ahead of our interceptor function being called to deal with the centering...
if(uroMarkers.type !== null)
{
uroDBG.AddLog('clicked on '+uroMarkers.type+' marker '+uroMarkers.id);
uroMarkers.clickedOnID = uroMarkers.id;
uroMarkers.clickedOnCenter = sdk.Map.getMapCenter();
if(uroMarkers.inhibitSetCenter === false)
{
if(uroMarkers.Decentre() === true)
{
uroMarkers.inhibitSetCenter = true;
}
}
}
},
Decentre: function()
{
let inhibit = false;
inhibit = inhibit || ((uroMarkers.type == uroLayersFT.ID.UR) && (uroUtils.GetCBChecked("_cbInhibitURCentering")));
inhibit = inhibit || ((uroMarkers.type == uroLayersFT.ID.MP) && (uroUtils.GetCBChecked("_cbInhibitMPCentering")));
inhibit = inhibit || ((uroMarkers.type == uroLayersFT.ID.VPUR) && (uroUtils.GetCBChecked("_cbInhibitPURCentering")));
inhibit = inhibit || ((uroMarkers.type == uroLayersFT.ID.VPUR) && (uroUtils.GetCBChecked("_cbInhibitPPURCentering")));
inhibit = inhibit || ((uroMarkers.type == uroLayersFT.ID.VPUR) && (uroUtils.GetCBChecked("_cbInhibitRPURCentering")));
return inhibit;
},
RegisterEvents: function()
{
let tracking = false;
for(let i = 0; i < uroLayers.layers.length; ++i)
{
if(uroLayers.layers[i].regEvt === true)
{
sdk.Events.trackLayerEvents({layerName: uroLayers.layers[i].name});
tracking = true;
}
}
if(tracking == true)
{
sdk.Events.on({eventName: "wme-layer-feature-mouse-enter", eventHandler: uroMarkers.MouseOver});
sdk.Events.on({eventName: "wme-layer-feature-mouse-leave", eventHandler: uroMarkers.MouseOut});
}
},
AddMarkerEventHandler: function()
{
if
(
(uroMarkers.type === uroLayersFT.ID.UR) ||
(uroMarkers.type === uroLayersFT.ID.MP) ||
(uroMarkers.type === uroLayersFT.ID.VPUR)
)
{
let mMarker = uroGetMarker(uroMarkers.type, uroMarkers.id);
if(mMarker !== null)
{
let mIcon = null;
if(mMarker.element !== undefined)
{
mIcon = mMarker.element;
}
else if(mMarker.geometry !== undefined)
{
mIcon = document.querySelector('#'+mMarker.geometry.id);
}
else if(mMarker.tagName === "image")
{
mIcon = mMarker;
}
if((mIcon !== null) && (mIcon !== undefined))
{
mIcon.addEventListener("mousedown", uroMarkers.MouseDown, false);
}
}
}
}
};
const uroLayersFT = // local IDs for all layers URO interacts with
{
ID :
{
VPUR: -1, // used to indicate any type of PUR marker - not directly associated with any of the above layer definitions
UR: 0,
MP: 1,
PUR: 2,
PPUR: 3,
RPUR: 4,
RTC: 5,
node: 6,
seg: 7,
venue: 8,
MC: 9,
RTCnode: 10,
TRTCnode: 11,
SegSug: 12,
EditSug: 13
}
};
const uroLayers = // layer functions
{
// -----------------------------------------------------------------
// NOTE CAREFULLY!
// The entries in layers and in the uroLayersFT.ID list MUST, MUST, MUST, remain
// in sync at all times...
// For each entry in layers, the following attributes are required:
//
// name = layer name as used by W.map and the SDK
// l = set to null, populated elsewhere
// mf = set to null, populated elsewhere
// MO = set to null, populated elsewhere
// regEvt = true if mouseover/leave handlers are required for this layer, false if not
// dm = name of the SDK datamodel section for the features on this layer, or null if SDK does not provide access
// gbi = name of the id parameter required by the getById() function, or null if sDK does not provide this
// ft = feature type value for the features on this layer, or null if not required
layers :
[
{
name: "update_requests",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: "MapUpdateRequests",
gbi: "mapUpdateRequestId",
ft: uroLayersFT.ID.UR,
getMF: true
},
{
name: "mapProblems",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: "MapProblems",
gbi: "mapProblemId",
ft: uroLayersFT.ID.MP,
getMF: true
},
{
name: "place_updates",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: null,
ft: uroLayersFT.ID.VPUR,
getMF: true
},
{
name: "PARKING_PLACE_UPDATES",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: null,
gbi: null,
ft: null,
getMF: true
},
{
name: "RESIDENTIAL_PLACE_UPDATES",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: null,
gbi: null,
ft: null,
getMF: true
},
{
name: "closures",
l: null,
mf: null,
MO: null,
regEvt: false,
dm: null,
gbi: null,
ft: null,
getMF: true
},
{
name: "nodes",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: "Nodes",
gbi: "nodeId",
ft: "node",
getMF: false
},
{
name: "segments",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: "Segments",
gbi: "segmentId",
ft: "segment",
getMF: false
},
{
name: "venues",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: "Venues",
gbi: "venueId",
ft: "venue",
getMF: false
},
{
name: "mapComments",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: "MapComments",
gbi: "mapCommentId",
ft: "comment",
getMF: false
},
{
name: "closure_nodes",
l: null,
mf: null,
MO: null,
regEvt: false,
dm: null,
gbi: null,
ft: null,
getMF: true
},
{
name: "turn_closure",
l: null,
mf: null,
MO: null,
regEvt: false,
dm: null,
gbi: null,
ft: null,
getMF: true
},
{
name: "segment_suggestions_markers",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: null,
gbi: null,
ft: uroLayersFT.ID.SegSug,
getMF: true
},
{
name: "edit_suggestions_markers",
l: null,
mf: null,
MO: null,
regEvt: true,
dm: "EditSuggestions",
gbi: "editSuggestionId",
ft: uroLayersFT.ID.EditSug,
getMF: true
}
],
// -----------------------------------------------------------------
LayerVisible: function(name)
{
let retval = null;
try
{
retval = sdk.Map.isLayerVisible({layerName: name});
}
catch
{
}
return retval;
},
BlobMouseOut: function()
{
let blobType = this.attributes.uroBlobType;
if(blobType !== undefined)
{
let blobID = this.attributes.uroBlobID;
uroDBG.AddLog('hover off '+blobType+' ID '+blobID);
if(blobType == 'map_comment')
{
let mcObj = sdk.DataModel.MapComments.getById({mapCommentId: blobID});
if(mcObj !== null)
{
let geoID = mcObj.geometry.id;
if(mcObj.geometry.type === 'Point')
{
// reapply visibility mods
window.setTimeout(uroLayers.ReapplyPointMCVisibilityMods,10);
}
}
}
}
else
{
uroDBG.AddLog('hover off unknown blob...');
}
},
MCLayerChanged_changed: function()
{
uroLayers.MCLayerChanged();
},
MCLayerChanged_added: function()
{
uroLayers.MCLayerChanged();
},
MCLayerChanged_removed: function()
{
uroLayers.MCLayerChanged();
},
ReapplyPointMCVisibilityMods: function()
{
if(uroLayers.ApplyPointMCVisibilityMods() === false)
{
window.setTimeout(uroLayers.ReapplyPointMCVisibilityMods,100);
}
},
ApplyPointMCVisibilityMods: function()
{
let retval = true;
if(uroLayers.HasSelectedMCs() === true)
{
retval = false;
}
else
{
let svgElm = document.getElementById(uroMCLayer.div.id+'_vroot');
for(let svgIdx = 0; svgIdx < svgElm.children.length; svgIdx++)
{
let svgChild = svgElm.children[svgIdx];
if(svgChild.id.indexOf('Point') != -1)
{
if(uroUtils.GetCBChecked('_cbMCEnhancePointMCVisibility') === true)
{
if(svgChild.getAttribute('r') == 6)
{
svgChild.setAttribute('fill','#ffff00');
svgChild.setAttribute('fill-opacity',0.75);
svgChild.setAttribute('r',12);
svgChild.setAttribute('touchedByURO',true);
}
else if((svgChild.getAttribute('touchedByURO') === "true")&&(svgChild.getAttribute('fill') === '#ffff00'))
{
// do nothing...
}
else
{
retval = false;
break;
}
}
else
{
if((svgChild.getAttribute('touchedByURO') === "true")&&(svgChild.getAttribute('fill') === '#ffff00'))
{
svgChild.setAttribute('fill','#ffffff');
svgChild.setAttribute('fill-opacity',1);
svgChild.setAttribute('r',6);
svgChild.setAttribute('touchedByURO',false);
}
}
}
}
}
return retval;
},
HasSelectedMCs: function()
{
let retval = false;
let selected = sdk.Editing.getSelection();
if(selected !== null)
{
if(selected.objectType === "mapComment")
{
retval = true;
}
}
return retval;
},
MCLayerChanged: function()
{
uroInit.WazeBits();
if(uroMCLayer != null)
{
if(uroLayers.HasSelectedMCs() === false)
{
uroDBG.AddLog('adding MC blob event handlers');
let mcModel = null;
for(let mObj=0; mObj<uroMCLayer.features.length; mObj++)
{
mcModel = uroMCLayer.features[mObj];
{
if(mcModel.renderIntent !== "select")
{
let mcBlobID = mcModel.geometry.id;
let mcID = mcModel.attributes.wazeFeature.id;
let mcBlob = document.getElementById(mcBlobID);
if(mcBlob !== null)
{
mcBlob.addEventListener("mouseout", uroLayers.BlobMouseOut, false);
mcBlob.attributes.uroBlobID = mcID;
mcBlob.attributes.uroBlobType = "map_comment";
uroDBG.AddLog('added handlers to MC '+mcID);
}
}
}
}
uroLayers.ApplyPointMCVisibilityMods();
}
else
{
uroDBG.AddLog('MC selected, handlers not added yet...');
}
uroFilterMapComments();
}
},
VenueLayerChanged: function()
{
uroDBG.AddLog('adding place blob event handlers');
for(let mObj=0; mObj<uroVenueLayer.features.length; mObj++)
{
// clicking on an area place now adds the polygon drag handles into the features[] array, so we need to test that
// the current array entry isn't referring to one of these handles before trying to access the attributes...
if(uroVenueLayer.features[mObj]?.attributes?.wazeFeature?._wmeObject !== undefined)
{
let mcBlobID = uroVenueLayer.features[mObj].attributes.wazeFeature._wmeObject.attributes.geometry.id;
let mcID = uroVenueLayer.features[mObj].attributes.wazeFeature._wmeObject.attributes.id;
let mcBlob = document.getElementById(mcBlobID);
if(mcBlob !== null)
{
mcBlob.addEventListener("mouseout", uroLayers.BlobMouseOut, false);
mcBlob.attributes.uroBlobID = mcID;
mcBlob.attributes.uroBlobType = "place";
}
}
}
},
Observe_VenueLayer: function()
{
uroLayers.layers[uroLayersFT.ID.venue].MO.observe(uroVenueLayer.div,{childList: true, attributes : true, characterData : true, subtree: true});
},
Observe_URLayer: function()
{
// As URs are now displayed as SVG image elements rather than HTML elements, and as WME likes to re-render them seemingly
// at random after they've been initially displayed, we hang the mutation observer off of the vectorRoot element, as this
// is the parent SVG element for the markers, and the MO therefore seems to trigger reliably on each change to that level
// of the SVG, including these random re-renders. It's obvious if these re-renders aren't being captured correctly, as it
// causes the comment count markers to randomly show up behind UR markers instead of always being in front of them...
uroLayers.layers[uroLayersFT.ID.UR].MO.observe(uroLayers.layers[uroLayersFT.ID.UR].l.renderer.vectorRoot,{childList: true, attributes : true, characterData : true, subtree: true});
},
URLayerChanged: function()
{
// Ignore any changes caused by unstacking markers...
if(uroStackType === null)
{
uroDBG.AddLog('UR layer change detected');
uroLayers.layers[uroLayersFT.ID.UR].MO.disconnect();
uroURSessions.Refresh();
uroLayers.Observe_URLayer();
}
},
PURLayerChanged: function()
{
uroDBG.AddLog('PUR layer change detected');
uroLayers.layers[uroLayersFT.ID.PUR].MO.disconnect();
uroFilterProblems();
uroLayers.Observe_PURLayer();
},
Observe_PURLayer: function()
{
uroLayers.layers[uroLayersFT.ID.PUR].MO.observe(uroLayers.layers[uroLayersFT.ID.PUR].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
},
PPURLayerChanged: function()
{
uroDBG.AddLog('PPUR layer change detected');
uroLayers.layers[uroLayersFT.ID.PPUR].MO.disconnect();
uroFilterProblems();
uroLayers.Observe_PPURLayer();
},
Observe_PPURLayer: function()
{
uroLayers.layers[uroLayersFT.ID.PPUR].MO.observe(uroLayers.layers[uroLayersFT.ID.PPUR].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
},
RPURLayerChanged: function()
{
uroDBG.AddLog('RPUR layer change detected');
uroLayers.layers[uroLayersFT.ID.RPUR].MO.disconnect();
uroFilterProblems();
uroLayers.Observe_RPURLayer();
},
Observe_RPURLayer: function()
{
uroLayers.layers[uroLayersFT.ID.RPUR].MO.observe(uroLayers.layers[uroLayersFT.ID.RPUR].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
},
RTCLayerChanged: function()
{
uroDBG.AddLog('reapplying closures filter');
uroLayers.layers[uroLayersFT.ID.RTC].MO.disconnect();
uroRTCs.Filter();
uroLayers.Observe_RTCLayer();
},
Observe_RTCLayer: function()
{
uroLayers.layers[uroLayersFT.ID.RTC].MO.observe(uroLayers.layers[uroLayersFT.ID.RTC].l.div,{childList: true, attributes : true, characterData : true, subtree: true});
},
RunChangeHandlers: function()
{
uroLayers.URLayerChanged();
uroLayers.PURLayerChanged();
uroLayers.PPURLayerChanged();
uroLayers.RPURLayerChanged();
uroLayers.VenueLayerChanged();
uroLayers.RTCLayerChanged();
uroLayers.MCLayerChanged();
},
InitialiseMOs: function()
{
uroLayers.layers[uroLayersFT.ID.UR].MO = new MutationObserver(uroLayers.URLayerChanged);
uroLayers.layers[uroLayersFT.ID.PUR].MO = new MutationObserver(uroLayers.PURLayerChanged);
uroLayers.layers[uroLayersFT.ID.PPUR].MO = new MutationObserver(uroLayers.PPURLayerChanged);
uroLayers.layers[uroLayersFT.ID.RPUR].MO = new MutationObserver(uroLayers.RPURLayerChanged);
uroLayers.layers[uroLayersFT.ID.venue].MO = new MutationObserver(uroLayers.VenueLayerChanged);
uroLayers.layers[uroLayersFT.ID.RTC].MO = new MutationObserver(uroLayers.RTCLayerChanged);
uroLayers.Observe_URLayer();
uroLayers.Observe_PURLayer();
uroLayers.Observe_PPURLayer();
uroLayers.Observe_RPURLayer();
uroLayers.Observe_VenueLayer();
uroLayers.Observe_RTCLayer();
},
GetMarkersOrFeatures: function(layerID)
{
let retval = null;
let findit = uroLayers.layers[layerID].l.features;
if(findit !== undefined)
{
retval = findit;
uroLayers.layers[layerID].isFeature = true;
uroDBG.AddLog(uroLayers.layers[layerID].name + ' = features');
}
else
{
findit = uroLayers.layers[layerID].l.markers;
if(findit !== undefined)
{
retval = findit;
uroLayers.layers[layerID].isFeature = false;
uroDBG.AddLog(uroLayers.layers[layerID].name + ' = markers');
}
}
if(retval === null)
{
uroLayers.layers[layerID].isFeature = null;
uroDBG.AddLog(uroLayers.layers[layerID].name + ' = unknown :-/');
}
return retval;
},
Init: function()
{
for(let i = 0; i < uroLayers.layers.length; ++i)
{
uroLayers.layers[i].l = W.map.getLayerByUniqueName(uroLayers.layers[i].name);
if(uroLayers.layers[i].getMF === true)
{
uroLayers.layers[i].mf = uroLayers.GetMarkersOrFeatures(i);
}
}
}
};
const uroAlertBox = // alert box handling
{
stack: [],
tickAction: null,
crossAction: null,
inUse: false,
ABObj: function(headericon, title, content, hasCross, tickText, crossText, tickAction, crossAction)
{
this.headericon = headericon;
this.title = title;
this.content = content;
this.hasCross = hasCross;
this.tickText = tickText;
this.crossText = crossText;
this.tickAction = tickAction;
this.crossAction = crossAction;
},
Close: function()
{
document.getElementById('uroAlerts').childNodes[0].innerHTML = uroUtils.ModifyHTML('');
document.getElementById('uroAlerts').childNodes[1].innerHTML = uroUtils.ModifyHTML('');
document.getElementById('uroAlertTickBtnCaption').innerHTML = uroUtils.ModifyHTML('');
document.getElementById('uroAlertCrossBtnCaption').innerHTML = uroUtils.ModifyHTML('');
uroAlertBox.tickAction = null;
uroAlertBox.crossAction = null;
document.getElementById('uroAlerts').style.visibility = "hidden";
document.getElementById('uroAlertCrossBtn').style.visibility = "hidden";
uroAlertBox.inUse = false;
if(uroAlertBox.stack.length > 0)
{
uroAlertBox.BuildFromStack();
}
},
CloseWithTick: function()
{
if(typeof uroAlertBox.tickAction === 'function')
{
uroAlertBox.tickAction();
}
uroAlertBox.Close();
},
CloseWithCross: function()
{
if(typeof uroAlertBox.crossAction === 'function')
{
uroAlertBox.crossAction();
}
uroAlertBox.Close();
},
Show: function(headericon, title, content, hasCross, tickText, crossText, tickAction, crossAction)
{
uroAlertBox.stack.push(new uroAlertBox.ABObj(headericon, title, content, hasCross, tickText, crossText, tickAction, crossAction));
if(uroAlertBox.inUse === false)
{
uroAlertBox.BuildFromStack();
}
},
BuildFromStack: function()
{
uroAlertBox.inUse = true;
uroAlertBox.tickAction = null;
uroAlertBox.crossAction = null;
let titleContent = '<span style="font-size:14px;padding:2px;">';
titleContent += '<i class="fa '+uroAlertBox.stack[0].headericon+'"> </i> ';
titleContent += uroAlertBox.stack[0].title;
titleContent += '</span>';
document.getElementById('uroAlerts').childNodes[0].innerHTML = uroUtils.ModifyHTML(titleContent);
document.getElementById('uroAlerts').childNodes[1].innerHTML = uroUtils.ModifyHTML(uroAlertBox.stack[0].content);
document.getElementById('uroAlertTickBtnCaption').innerHTML = uroUtils.ModifyHTML(uroAlertBox.stack[0].tickText);
if(uroAlertBox.stack[0].hasCross)
{
document.getElementById('uroAlertCrossBtnCaption').innerHTML = uroUtils.ModifyHTML(uroAlertBox.stack[0].crossText);
document.getElementById('uroAlertCrossBtn').style.visibility = "visible";
if(typeof uroAlertBox.stack[0].crossAction === "function")
{
uroAlertBox.crossAction = uroAlertBox.stack[0].crossAction;
}
}
else
{
document.getElementById('uroAlertCrossBtn').style.visibility = "hidden";
}
if(typeof uroAlertBox.stack[0].tickAction === "function")
{
uroAlertBox.tickAction = uroAlertBox.stack[0].tickAction;
}
document.getElementById('uroAlerts').style.visibility = "";
uroAlertBox.stack.shift();
}
};
const uroStartup = // startup messaging to users
{
ShowUpgradeNotes: function()
{
uroDBG.AddLog('let users know what\'s new in this release');
let releaseNotes = '';
releaseNotes += '<p>Thanks for installing URO+ '+uroRelease.version+' ('+uroRelease.date+')</p>';
let loop;
if(uroRelease.changes.length > 0)
{
releaseNotes += '<br>Changes since the last release:<br>';
releaseNotes += '<ul>';
for(loop=0; loop < uroRelease.changes.length; loop++)
{
releaseNotes += '<li>'+uroRelease.changes[loop];
}
releaseNotes += '</ul>';
}
uroAlertBox.Show('fa-info-circle', 'URO+ Release Notes', releaseNotes, false, "OK", "", null, null);
}
};
const uroConfig = // configuration handling
{
GatherSettings: function(container)
{
let options = '';
if(typeof(container) == 'string')
{
container = document.getElementById(container);
}
let urOptions = container.getElementsByTagName('input');
for (let optIdx=0;optIdx<urOptions.length;optIdx++)
{
// Don't save settings for any of the legacy input elements we've now hidden...
if(urOptions[optIdx].style.display != "none")
{
let id = urOptions[optIdx].id;
if((id.indexOf('_cb') === 0)||(id.indexOf('_text') === 0)||(id.indexOf('_input') === 0))
{
options += ':' + id;
if(urOptions[optIdx].type == 'checkbox') options += ',' + urOptions[optIdx].checked.toString();
else if((urOptions[optIdx].type == 'text')||(urOptions[optIdx].type == 'number')) options += ',' + urOptions[optIdx].value.toString();
}
}
}
return options;
},
GatherPlacesGroups: function()
{
let liststr = '';
for(let loop=0;loop<uroPlacesGroupsCollapsed.length;loop++)
{
if(loop > 0) liststr += ':';
liststr += uroPlacesGroupsCollapsed[loop];
}
return liststr;
},
GatherAFNs: function()
{
let liststr = '';
for(let loop=0; loop < uroAFN.friendlyNames.length; loop++)
{
let fnObj = uroAFN.friendlyNames[loop];
if(loop > 0) liststr += ':';
liststr += fnObj.fName+',';
liststr += fnObj.area+',';
liststr += fnObj.server;
}
return liststr;
},
SaveSettings: function()
{
if((uroInhibitSave) || (uroMTEMode === true) || (uroSettingsApplied === false))
{
uroDBG.AddLog('save inhibited');
return;
}
if (localStorage)
{
try
{
let masterEnable = uroUtils.GetCBChecked('_cbMasterEnable');
if(masterEnable !== null)
{
for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
{
localStorage[uroTabs.CtrlTabs[i][uroTabs.FIELDS.STORAGE]] = uroConfig.GatherSettings(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY]);
}
localStorage.UROverviewFriendlyAreaNames = uroConfig.GatherAFNs();
localStorage.UROverviewPlacesGroups = uroConfig.GatherPlacesGroups();
localStorage.UROverviewMasterEnable = masterEnable;
localStorage.UROverviewCurrentVersion = uroRelease.version;
uroDBG.AddLog('settings saved');
}
else
{
uroDBG.AddLog('master enable state not known when trying to save settings...');
}
}
catch(err)
{
uroDBG.AddLog('exception thrown during save - probably script reload whilst in MTE mode...');
}
}
else
{
uroDBG.AddLog('no localStorage, save blocked');
}
},
ApplySettings: function(settings)
{
uroSettingsApplied = true;
if(settings != undefined)
{
if(document.querySelector('#_cbMasterEnable') === null)
{
uroDBG.AddLog('master enable not found, not applying settings...');
uroSettingsApplied = false;
}
else
{
let options = settings.split(':');
for(let optIdx=0;optIdx<options.length;optIdx++)
{
let fields = options[optIdx].split(',');
if(fields[0].indexOf('_cb') === 0)
{
if(document.getElementById(fields[0]) !== null)
{
uroUtils.SetCBChecked(fields[0], (fields[1] == 'true'));
}
}
else if((fields[0].indexOf('_input') === 0)||(fields[0].indexOf('_text') === 0))
{
if(document.getElementById(fields[0]) !== null) document.getElementById(fields[0]).value = fields[1];
}
}
uroDBG.AddLog('settings applied...');
}
}
},
TranslateLegacyMPTab: function()
{
let options = localStorage.UROverviewMPOptions.split(':');
for(let optIdx=0;optIdx<options.length;optIdx++)
{
let fields = options[optIdx].split(',');
if(fields[0].indexOf('_cb') === 0)
{
if(fields[0] == '_cbMPFilterParkingLotInputAsPoint') uroUtils.SetCBChecked('_cbMPFilter_T50', (fields[1] == 'true'));
if(fields[0] == '_cbMPMissingPLP_T70') uroUtils.SetCBChecked('_cbMPFilter_T70', (fields[1] == 'true'));
if(fields[0] == '_cbMPMissingPLP_T71') uroUtils.SetCBChecked('_cbMPFilter_T71', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterDrivingDirectionMismatch') uroUtils.SetCBChecked('_cbMPFilter_T101', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterMissingJunction') uroUtils.SetCBChecked('_cbMPFilter_T102', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterMissingRoad') uroUtils.SetCBChecked('_cbMPFilter_T103', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterCrossroadsJunctionMissing') uroUtils.SetCBChecked('_cbMPFilter_T104', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterRoadTypeMismatch') uroUtils.SetCBChecked('_cbMPFilter_T105', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterRestrictedTurn') uroUtils.SetCBChecked('_cbMPFilter_T106', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterTurnProblem') uroUtils.SetCBChecked('_cbMPFilter_T200', (fields[1] == 'true'));
if(fields[0] == '_cbMPFilterRoadClosureProblem') uroUtils.SetCBChecked('_cbMPFilter_T300', (fields[1] == 'true'));
}
}
},
TranslateLegacyZoom: function()
{
let tZoom = parseInt(document.getElementById("_inputFilterMinZoomLevel").value);
if(tZoom < 12)
{
tZoom += 12;
document.getElementById("_inputFilterMinZoomLevel").value = tZoom;
}
tZoom = parseInt(document.getElementById("_inputUnstackZoomLevel").value);
if(tZoom < 12)
{
tZoom += 12;
document.getElementById("_inputUnstackZoomLevel").value = tZoom;
}
},
RemoveOldSettings: function()
{
let oldies = [
"UROverviewFeedFilterOptions",
"UROverviewCWLGroups",
"UROverviewCamWatchList",
"UROverviewPlaceWatchList",
"UROverviewSegWatchList"
];
for(let i = 0; i < oldies.length; ++ i)
{
if(localStorage[oldies[i]] != undefined)
{
localStorage.removeItem(oldies[i]);
}
}
},
LoadSettings: function()
{
let isNewInstall = true;
let isUpgradeInstall = true;
uroDBG.AddLog('loadSettings()');
for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
{
if (uroTabs.CtrlTabs[i][uroTabs.FIELDS.STORAGE] != null)
{
uroDBG.AddLog('recover '+uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABTITLE]+' tab settings');
uroConfig.ApplySettings(localStorage[uroTabs.CtrlTabs[i][uroTabs.FIELDS.STORAGE]]);
isNewInstall = false;
}
}
if (localStorage.UROverviewMPOptions != null)
{
uroConfig.TranslateLegacyMPTab();
}
if (localStorage.UROverviewMiscOptions != null)
{
uroConfig.TranslateLegacyZoom();
}
if(localStorage.UROverviewCurrentVersion != null)
{
uroDBG.AddLog('comparing install versions');
if(localStorage.UROverviewCurrentVersion == uroRelease.version)
{
isUpgradeInstall = false;
}
}
if(localStorage.UROverviewFriendlyAreaNames != null)
{
uroDBG.AddLog('recover friendly area names');
uroAFN.ApplyNames();
isNewInstall = false;
}
if(localStorage.UROverviewMasterEnable != null)
{
uroDBG.AddLog('recover master enable state');
document.getElementById('_cbMasterEnable').checked = (localStorage.UROverviewMasterEnable == "true");
uroDBG.AddLog('enable checkbox state set...');
}
uroConfig.RemoveOldSettings();
if((isNewInstall)||(isUpgradeInstall))
{
uroStartup.ShowUpgradeNotes();
}
uroInhibitSave = false;
},
DefaultSettings: function()
{
uroAlertBox.Show("fa-warning", "URO+ Warning", "Resetting URO+ settings <b>cannot</b> be undone.<br>Are you <i>sure</i> you want to do this?", true, "Reset settings", "Keep settings", uroConfig.DefaultSettingsAction, null);
},
DefaultSettingsAction: function()
{
let defaultSettings = '';
defaultSettings += '[UROverviewUROptions][len=1849]:_cbURFilterOutsideArea,false:_cbURFilterInsideManagedAreas,false:_cbURExcludeUserArea,false:_cbNoFilterForURInURL,false:_cbURFilterDupes,false:_cbFilterWazeAuto,false:_cbFilterIncorrectTurn,false:_cbFilterIncorrectAddress,false:_cbFilterIncorrectRoute,false:_cbFilterMissingRoundabout,false:_cbFilterGeneralError,false:_cbFilterTurnNotAllowed,false:_cbFilterIncorrectJunction,false:_cbFilterMissingBridgeOverpass,false:_cbFilterWrongDrivingDirection,false:_cbFilterMissingExit,false:_cbFilterMissingRoad,false:_cbFilterBlockedRoad,false:_cbFilterMissingLandmark,false:_cbFilterSpeedLimits,false:_cbFilterUndefined,false:_cbFilterRoadworks,false:_cbFilterConstruction,false:_cbFilterClosure,false:_cbFilterEvent,false:_cbFilterNote,false:_cbFilterBOG,false:_cbFilterDifficult,false:_cbFilterWSLM,false:_cbInvertURFilter,false:_cbFilterOpenUR,false:_cbFilterClosedUR,false:_cbFilterSolved,false:_cbFilterUnidentified,false:_cbEnableMinAgeFilter,false:_inputFilterMinDays,60:_cbEnableMaxAgeFilter,false:_inputFilterMaxDays,62:_cbHideMyFollowed,false:_cbHideMyUnfollowed,false:_cbURDescriptionMustBePresent,false:_cbURDescriptionMustBeAbsent,false:_cbEnableKeywordMustBePresent,false:_textKeywordPresent,:_cbEnableKeywordMustBeAbsent,false:_textKeywordAbsent,:_cbCaseInsensitive,false:_cbHideMyComments,false:_cbHideAnyComments,false:_cbHideIfLastCommenter,false:_cbHideIfNotLastCommenter,false:_cbHideIfReporterLastCommenter,false:_cbHideIfReporterNotLastCommenter,false:_cbEnableMinCommentsFilter,false:_inputFilterMinComments,1:_cbEnableMaxCommentsFilter,false:_inputFilterMaxComments,0:_cbEnableCommentAgeFilter2,false:_inputFilterCommentDays2,:_cbEnableCommentAgeFilter,false:_inputFilterCommentDays,1:_cbIgnoreOtherEditorComments,false:_cbURUserIDFilter,false:_cbURResolverIDFilter,false:_cbInvertURStateFilter,false:_cbNoFilterForTaggedURs,false[END]';
defaultSettings += '[UROverviewMiscOptions][len=1157]:_cbHideSegmentsWhenRoadsHidden,false:_cbKillInertialPanning,false:_cbCommentCount,false:_cbAutoApplyClonedClosure,false:_cbAutoScrollClosureList,false:_inputFilterMinZoomLevel,22:_inputUnstackSensitivity,30:_inputUnstackZoomLevel,22:_inputPopupEntryTimeout,10:_inputPopupExitTimeout,2:_inputPopupAutoHideTimeout,0:_cbInhibitURClusters,false:_cbInhibitMPClusters,false:_cbInhibitPUClusters,false:_cbInhibitURPopup,false:_cbInhibitMPPopup,false:_cbInhibitCamPopup,false:_cbInhibitSegPopup,false:_cbInhibitSegGenericPopup,false:_cbInhibitLandmarkPopup,false:_cbInhibitPUPopup,false:_cbInhibitMapCommentPopup,false:_cbInhibitNodesPopup,false:_cbDateFmtDDMMYY,true:_cbDateFmtMMDDYY,false:_cbDateFmtYYMMDD,false:_cbTimeFmt24H,true:_cbTimeFmt12H,false:_cbWhiteBackground,false:_inputCustomBackgroundRed,30:_inputCustomBackgroundGreen,30:_inputCustomBackgroundBlue,30:_cbInhibitNURButton,false:_cbInhibitNMPButton,false:_cbInhibitNPURButton,false:_cbInhibitURCentering,false:_cbInhibitMPCentering,false:_cbInhibitPURCentering,false:_cbInhibitPPURCentering,false:_cbInhibitRPURCentering,false:_cbHideAMLayer,false:_cbMoveAMList,false:_cbDisablePlacesFiltering,false[END]';
defaultSettings += '[UROverviewPlacesOptions][len=6292]:_cbFilterUneditablePlaceUpdates,false:_cbPURFilterInsideManagedAreas,false:_cbPURExcludeUserArea,false:_cbFilterLockRankedPlaceUpdates,false:_cbFilterNewPlacePUR,false:_cbFilterUpdatedDetailsPUR,false:_cbPURFilterCFPhone,false:_cbPURFilterCFName,false:_cbPURFilterCFEntryExitPoints,false:_cbPURFilterCFOpeningHours,false:_cbPURFilterCFAliases,false:_cbPURFilterCFServices,false:_cbPURFilterCFGeometry,false:_cbPURFilterCFHouseNumber,false:_cbPURFilterCFCategories,false:_cbPURFilterCFDescription,false:_cbFilterNewPhotoPUR,false:_cbFilterFlaggedPUR,false:_cbInvertPURFilters,false:_cbEnablePURMinAgeFilter,false:_inputPURFilterMinDays,3:_cbEnablePURMaxAgeFilter,false:_inputPURFilterMaxDays,4:_cbPlaceFilterEditedLessThan,false:_inputFilterPlaceEditMinDays,:_cbPlaceFilterEditedMoreThan,false:_inputFilterPlaceEditMaxDays,:_cbHidePlacesL0,false:_cbHidePlacesL1,false:_cbHidePlacesL2,false:_cbHidePlacesL3,false:_cbHidePlacesL4,false:_cbHidePlacesL5,false:_cbHidePlacesStaff,false:_cbHidePlacesAdLocked,false:_cbHideAreaPlaces,false:_cbHidePointPlaces,false:_cbHidePhotoPlaces,false:_cbHideNoPhotoPlaces,false:_cbHideLinkedPlaces,false:_cbHideNoLinkedPlaces,false:_cbHideDescribedPlaces,false:_cbHideNonDescribedPlaces,false:_cbHideKeywordPlaces,false:_cbHideNoKeywordPlaces,false:_textKeywordPlace,:_cbShowOnlyPlacesCreatedBy,false:_cbShowOnlyPlacesEditedBy,false:_textPlacesEditor,theMadcabbie:_cbHideOnlyPlacesCreatedBy,false:_cbHideOnlyPlacesEditedBy,false:_textHidePlacesEditor,theMadcabbie:_cbLeavePURGeos,false:_cbHidePURsForFilteredPlaces,false:_cbPlacesFilter-CAR_SERVICES,false:_cbPlacesFilter-CAR_WASH,false:_cbPlacesFilter-CHARGING_STATION,false:_cbPlacesFilter-GARAGE_AUTOMOTIVE_SHOP,false:_cbPlacesFilter-GAS_STATION,false:_cbPlacesFilter-CRISIS_LOCATIONS,false:_cbPlacesFilter-DONATION_CENTERS,false:_cbPlacesFilter-OTHER_CRISIS_LOCATIONS,false:_cbPlacesFilter-SHELTER_LOCATIONS,false:_cbPlacesFilter-CULTURE_AND_ENTERTAINEMENT,false:_cbPlacesFilter-ART_GALLERY,false:_cbPlacesFilter-CASINO,false:_cbPlacesFilter-CLUB,false:_cbPlacesFilter-TOURIST_ATTRACTION_HISTORIC_SITE,false:_cbPlacesFilter-MOVIE_THEATER,false:_cbPlacesFilter-MUSEUM,false:_cbPlacesFilter-MUSIC_VENUE,false:_cbPlacesFilter-PERFORMING_ARTS_VENUE,false:_cbPlacesFilter-GAME_CLUB,false:_cbPlacesFilter-STADIUM_ARENA,false:_cbPlacesFilter-THEME_PARK,false:_cbPlacesFilter-ZOO_AQUARIUM,false:_cbPlacesFilter-RACING_TRACK,false:_cbPlacesFilter-THEATER,false:_cbPlacesFilter-FOOD_AND_DRINK,false:_cbPlacesFilter-RESTAURANT,false:_cbPlacesFilter-BAKERY,false:_cbPlacesFilter-DESSERT,false:_cbPlacesFilter-CAFE,false:_cbPlacesFilter-FAST_FOOD,false:_cbPlacesFilter-FOOD_COURT,false:_cbPlacesFilter-BAR,false:_cbPlacesFilter-ICE_CREAM,false:_cbPlacesFilter-LODGING,false:_cbPlacesFilter-HOTEL,false:_cbPlacesFilter-HOSTEL,false:_cbPlacesFilter-CAMPING_TRAILER_PARK,false:_cbPlacesFilter-COTTAGE_CABIN,false:_cbPlacesFilter-BED_AND_BREAKFAST,false:_cbPlacesFilter-NATURAL_FEATURES,false:_cbPlacesFilter-ISLAND,false:_cbPlacesFilter-SEA_LAKE_POOL,false:_cbPlacesFilter-RIVER_STREAM,false:_cbPlacesFilter-FOREST_GROVE,false:_cbPlacesFilter-FARM,false:_cbPlacesFilter-CANAL,false:_cbPlacesFilter-SWAMP_MARSH,false:_cbPlacesFilter-DAM,false:_cbPlacesFilter-OTHER,false:_cbPlacesFilter-CONSTRUCTION_SITE,false:_cbPlacesFilter-OUTDOORS,false:_cbPlacesFilter-PARK,false:_cbPlacesFilter-PLAYGROUND,false:_cbPlacesFilter-BEACH,false:_cbPlacesFilter-SPORTS_COURT,false:_cbPlacesFilter-GOLF_COURSE,false:_cbPlacesFilter-PLAZA,false:_cbPlacesFilter-PROMENADE,false:_cbPlacesFilter-POOL,false:_cbPlacesFilter-SCENIC_LOOKOUT_VIEWPOINT,false:_cbPlacesFilter-SKI_AREA,false:_cbPlacesFilter-PARKING_LOT,false:_cbPlacesFilter-PROFESSIONAL_AND_PUBLIC,false:_cbPlacesFilter-COLLEGE_UNIVERSITY,false:_cbPlacesFilter-SCHOOL,false:_cbPlacesFilter-CONVENTIONS_EVENT_CENTER,false:_cbPlacesFilter-GOVERNMENT,false:_cbPlacesFilter-LIBRARY,false:_cbPlacesFilter-CITY_HALL,false:_cbPlacesFilter-ORGANIZATION_OR_ASSOCIATION,false:_cbPlacesFilter-PRISON_CORRECTIONAL_FACILITY,false:_cbPlacesFilter-COURTHOUSE,false:_cbPlacesFilter-CEMETERY,false:_cbPlacesFilter-FIRE_DEPARTMENT,false:_cbPlacesFilter-POLICE_STATION,false:_cbPlacesFilter-MILITARY,false:_cbPlacesFilter-HOSPITAL_URGENT_CARE,false:_cbPlacesFilter-DOCTOR_CLINIC,false:_cbPlacesFilter-OFFICES,false:_cbPlacesFilter-POST_OFFICE,false:_cbPlacesFilter-RELIGIOUS_CENTER,false:_cbPlacesFilter-KINDERGARDEN,false:_cbPlacesFilter-FACTORY_INDUSTRIAL,false:_cbPlacesFilter-EMBASSY_CONSULATE,false:_cbPlacesFilter-INFORMATION_POINT,false:_cbPlacesFilter-EMERGENCY_SHELTER,false:_cbPlacesFilter-TRASH_AND_RECYCLING_FACILITIES,false:_cbPlacesFilter-SHOPPING_AND_SERVICES,false:_cbPlacesFilter-ARTS_AND_CRAFTS,false:_cbPlacesFilter-BANK_FINANCIAL,false:_cbPlacesFilter-SPORTING_GOODS,false:_cbPlacesFilter-BOOKSTORE,false:_cbPlacesFilter-PHOTOGRAPHY,false:_cbPlacesFilter-CAR_DEALERSHIP,false:_cbPlacesFilter-FASHION_AND_CLOTHING,false:_cbPlacesFilter-CONVENIENCE_STORE,false:_cbPlacesFilter-PERSONAL_CARE,false:_cbPlacesFilter-DEPARTMENT_STORE,false:_cbPlacesFilter-PHARMACY,false:_cbPlacesFilter-ELECTRONICS,false:_cbPlacesFilter-FLOWERS,false:_cbPlacesFilter-FURNITURE_HOME_STORE,false:_cbPlacesFilter-GIFTS,false:_cbPlacesFilter-GYM_FITNESS,false:_cbPlacesFilter-SWIMMING_POOL,false:_cbPlacesFilter-HARDWARE_STORE,false:_cbPlacesFilter-MARKET,false:_cbPlacesFilter-SUPERMARKET_GROCERY,false:_cbPlacesFilter-JEWELRY,false:_cbPlacesFilter-LAUNDRY_DRY_CLEAN,false:_cbPlacesFilter-SHOPPING_CENTER,false:_cbPlacesFilter-MUSIC_STORE,false:_cbPlacesFilter-PET_STORE_VETERINARIAN_SERVICES,false:_cbPlacesFilter-TOY_STORE,false:_cbPlacesFilter-TRAVEL_AGENCY,false:_cbPlacesFilter-ATM,false:_cbPlacesFilter-CURRENCY_EXCHANGE,false:_cbPlacesFilter-CAR_RENTAL,false:_cbPlacesFilter-TELECOM,false:_cbPlacesFilter-TRANSPORTATION,false:_cbPlacesFilter-AIRPORT,false:_cbPlacesFilter-BUS_STATION,false:_cbPlacesFilter-FERRY_PIER,false:_cbPlacesFilter-SEAPORT_MARINA_HARBOR,false:_cbPlacesFilter-SUBWAY_STATION,false:_cbPlacesFilter-TRAIN_STATION,false:_cbPlacesFilter-BRIDGE,false:_cbPlacesFilter-TUNNEL,false:_cbPlacesFilter-TAXI_STATION,false:_cbPlacesFilter-JUNCTION_INTERCHANGE,false:_cbPlacesFilter-REST_AREAS,false:_cbPlacesFilter-CARPOOL_SPOT,false:_cbFilterPrivatePlaces,false:_cbInvertPlacesFilter,false[END]';
defaultSettings += '[UROverviewPlacesGroups][len=71]false:false:false:false:false:false:false:false:false:false:false:false[END]';
defaultSettings += '[UROverviewMPOptions][len=1446]:_cbMPFilterOutsideArea,false:_cbMPFilter_T1,false:_cbMPFilter_T2,false:_cbMPFilter_T3,false:_cbMPFilter_T5,false:_cbMPFilter_T6,false:_cbMPFilter_T7,false:_cbMPFilter_T8,false:_cbMPFilter_T10,false:_cbMPFilter_T11,false:_cbMPFilter_T12,false:_cbMPFilter_T13,false:_cbMPFilter_T14,false:_cbMPFilter_T15,false:_cbMPFilter_T16,false:_cbMPFilter_T17,false:_cbMPFilter_T19,false:_cbMPFilter_T20,false:_cbMPFilter_T21,false:_cbMPFilter_T22,false:_cbMPFilter_T23,false:_cbMPFilter_T50,false:_cbMPFilter_T51,false:_cbMPFilter_T52,false:_cbMPFilter_T53,false:_cbMPFilter_T70,false:_cbMPFilter_T71,false:_cbMPFilter_T101,false:_cbMPFilter_T102,false:_cbMPFilter_T103,false:_cbMPFilter_T104,false:_cbMPFilter_T105,false:_cbMPFilter_T106,false:_cbMPFilter_T200,false:_cbMPFilter_T300,false:_cbMPFilterUnknownProblem,false:_cbFilterElgin,false:_cbFilterTrafficCast,false:_cbFilterTrafficMaster,false:_cbFilterCaltrans,false:_cbFilterTFL,false:_cbMPFilterReopenedProblem,false:_cbInvertMPFilter,false:_cbMPFilterClosed,false:_cbMPFilterSolved,false:_cbMPFilterUnidentified,false:_cbMPClosedUserIDFilter,false:_cbMPNotClosedUserIDFilter,false:_cbMPFilterLowSeverity,false:_cbMPFilterMediumSeverity,false:_cbMPFilterHighSeverity,false:_cbMPFilterStartDate,false:_inputMPFilterStartDay,:_inputMPFilterStartMonth,:_inputMPFilterStartYear,:_cbMPFilterEndDate,false:_inputMPFilterEndDay,:_inputMPFilterEndMonth,:_inputMPFilterEndYear,:_cbMPFilterEndDatePassed,false[END]';
defaultSettings += '[UROverviewMasterEnable][len=4]true[END]';
defaultSettings += '[UROverviewMCOptions][len=828]:_cbMCFilterRoadworks,false:_cbMCFilterConstruction,false:_cbMCFilterClosure,false:_cbMCFilterEvent,false:_cbMCFilterNote,false:_cbMCFilterBOG,false:_cbMCFilterDifficult,false:_cbMCFilterWSLM,false:_cbInvertMCFilter,false:_cbMCHideMyFollowed,false:_cbMCHideMyUnfollowed,false:_cbMCDescriptionMustBePresent,false:_cbMCDescriptionMustBeAbsent,false:_cbMCCommentsMustBePresent,false:_cbMCCommentsMustBeAbsent,false:_cbMCExpiryMustBePresent,false:_cbMCExpiryMustBeAbsent,false:_cbMCEnableKeywordMustBePresent,false:_textMCKeywordPresent,:_cbMCEnableKeywordMustBeAbsent,false:_textMCKeywordAbsent,:_cbMCCaseInsensitive,false:_cbMCCreatorIDFilter,false:_cbHideWRCMCs,false:_cbHideMCRank0,false:_cbHideMCRank1,false:_cbHideMCRank2,false:_cbHideMCRank3,false:_cbHideMCRank4,false:_cbHideMCRank5,false:_cbMCEnhancePointMCVisibility,false[END]';
defaultSettings += '[UROverviewRTCOptions][len=710]:_cbHideExpiredEditorRTCs,false:_cbHideEditorRTCs,false:_cbHideFutureEditorRTCs,false:_cbHideExpiredWazeFeedRTCs,false:_cbHideWazeFeedRTCs,false:_cbHideFutureWazeFeedRTCs,false:_cbHideExpiredWazeRTCs,false:_cbHideWazeRTCs,false:_cbHideFutureWazeRTCs,false:_cbHideExpiredSidepanelRTCs,false:_cbHideSidepanelRTCs,false:_cbHideFutureSidepanelRTCs,false:_cbShowMTERTCs,false:_cbHideMTERTCs,false:_cbEnableRTCDurationFilterLessThan,false:_inputFilterRTCDurationLessThan,:_cbEnableRTCDurationFilterMoreThan,false:_inputFilterRTCDurationMoreThan,:_cbRTCFilterShowForTS,false:_cbRTCFilterHideForTS,false:_inputRTCFilterDay,15:_inputRTCFilterMonth,6:_inputRTCFilterYear,2024:_inputRTCFilterHour,11:_inputRTCFilterMin,15[END]';
defaultSettings += '[UROverviewRAOptions][len=178]:_cbShowSpecificRA,false:_cbRAEditorIDFilter,false:_cbEnableRAAgeFilterLessThan,false:_inputFilterRAAgeLessThan,39:_cbEnableRAAgeFilterMoreThan,false:_inputFilterRAAgeMoreThan,38[END]';
defaultSettings += '[UROverviewCurrentVersion][len=3]4.5[END]';
defaultSettings += '[UROverviewFriendlyAreaNames][len=0][END]';
document.getElementById('_txtSettings').value = defaultSettings;
uroConfig.TextToSettings();
document.getElementById('_txtSettings').value = '';
},
SettingsToText: function()
{
let txtSettings = '';
uroConfig.SaveSettings();
for(let lsEntry in localStorage)
{
if(lsEntry.indexOf('UROverview') === 0)
{
txtSettings += '['+lsEntry+'][len=' + localStorage[lsEntry].length + ']' + localStorage[lsEntry] + '[END]\n';
}
}
document.getElementById('_txtSettings').value = txtSettings;
document.getElementById('_txtSettings').focus();
document.getElementById('_txtSettings').select();
},
TextToSettings: function()
{
let txtSettings = '';
txtSettings = uroUtils.GetElmValue('_txtSettings');
if(txtSettings.indexOf('[END]') == -1) return;
let subText = txtSettings.split('[END]');
for(let i=0;i<subText.length;i++)
{
let aPos = subText[i].indexOf('[');
let bPos = subText[i].indexOf(']');
if((aPos != -1) && (bPos != -1))
{
let settingID = subText[i].substring(aPos+1, bPos);
subText[i] = subText[i].substring(bPos+1);
bPos = subText[i].indexOf(']');
if(bPos != -1)
{
let settingLength = subText[i].substring(5, bPos);
subText[i] = subText[i].substring(bPos+1);
if(subText[i].length == settingLength)
{
localStorage[settingID] = subText[i];
}
}
}
}
uroConfig.LoadSettings();
},
ClearSettingsText: function()
{
document.getElementById('_txtSettings').value = '';
}
};
const uroRTCClone = // RTC cloning
{
ConfirmDelete : true,
Reason : null,
Event : null,
Direction : null,
StartDate : null,
StartTime : null,
EndDate : null,
EndTime : null,
IgnoreTraffic : null,
ClosedNodes : null,
PendingClone : -1,
PendingCloneIncrement : 0,
SetReact: function(elmDesc, newValue)
{
let elm = document.querySelector(elmDesc);
if(elm !== null)
{
// Some (all?) of the closure panel elements need to be focussed before we can successfully change
// their values...
elm.focus();
// https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js#46012210
let nativeInputValueSetter = Object.getOwnPropertyDescriptor(elm.__proto__, "value").set;
nativeInputValueSetter.call(elm, newValue);
let event = new Event('input', { bubbles: true });
elm.dispatchEvent(event);
}
},
Complete: function()
{
let loop;
if(document.getElementsByClassName('edit-closure').length === 0)
{
window.setTimeout(uroRTCClone.Complete,100);
return;
}
/*
if(uroRTCs.FixMTEDropDown(document.getElementById('closure_eventId')) == false)
{
window.setTimeout(uroRTCClone.Complete,100);
return;
}
*/
if(uroRTCClone.Reason !== null)
{
uroRTCClone.SetReact('wz-text-input#closure_reason', uroRTCClone.Reason);
}
if(uroRTCClone.Direction !== null)
{
uroRTCClone.SetReact('wz-select#closure_direction', uroRTCClone.Direction);
}
if(uroRTCClone.StartDate !== null)
{
uroRTCClone.SetReact('wz-text-input#closure_startDate', uroRTCClone.StartDate);
}
if(uroRTCClone.StartTime !== null)
{
uroRTCClone.SetReact('div.form-group.start-date-form-group wz-text-input.time-picker-input', uroRTCClone.StartTime);
}
if(uroRTCClone.IgnoreTraffic !== null)
{
uroRTCClone.SetReact('wz-checkbox#closure_permanent', uroRTCClone.IgnoreTraffic);
}
if(uroRTCClone.EndDate !== null)
{
uroRTCClone.SetReact('wz-text-input#closure_endDate', uroRTCClone.EndDate);
}
if(uroRTCClone.EndTime !== null)
{
uroRTCClone.SetReact('div.form-group.end-date-form-group wz-text-input.time-picker-input', uroRTCClone.EndTime);
}
// the old method of setting the MTE just by changing the value attribute on closure_eventId no longer
// seems to work as expected (it runs OK from the dev console, but not within the scope of the userscript),
// so just as we do for setting the event to None, we set the event to the desired value here by finding
// the appropriate menu entry and clicking on it...
let cEvents = document.getElementById('closure_eventId').getElementsByTagName('wz-option');
for(let i of cEvents)
{
if(i.value == uroRTCClone.Event)
{
i.click();
break;
}
}
let nNodes = uroRTCClone.ClosedNodes.length;
if(nNodes > 0)
{
let form = $('#edit-panel .closures .edit-closure form');
let fObj = form.find('wz-toggle-switch');
for(loop = 0; loop < nNodes; ++loop)
{
if(uroRTCClone.ClosedNodes[loop] === true)
{
fObj[loop].click();
}
}
}
if(uroUtils.GetCBChecked('_cbAutoApplyClonedClosure') == true)
{
window.setTimeout(uroRTCClone.ClickSave,100);
}
uroRTCClone.PendingClone = -1;
},
ClickSave: function()
{
document.getElementsByClassName('closures')[0].getElementsByClassName('save-button')[0].click();
},
Copy: function()
{
// grab the current closure details from the UI...
uroRTCClone.Reason = uroGetShadowElementProperty('closure_reason', 'input', 'value');
uroRTCClone.Direction = uroGetElementProperty('closure_direction', 0, 'value');
uroRTCClone.StartDate = uroGetElementProperty('closure_startDate', 0, 'value');
uroRTCClone.StartTime = document.querySelector('.start-date-form-group').querySelector('.time-picker-input').value;
uroRTCClone.EndDate = uroGetElementProperty('closure_endDate', 0, 'value');
uroRTCClone.EndTime = document.querySelector('.end-date-form-group').querySelector('.time-picker-input').value;
uroRTCClone.Event = uroGetElementProperty('closure_eventId', 0, 'value');
uroRTCClone.IgnoreTraffic = uroGetElementProperty('closure_permanent', 0, 'checked');
uroRTCClone.ClosedNodes = [];
let nNodes = document.getElementsByClassName('fromNodeClosed').length;
if(nNodes > 0)
{
for(let loop = 0; loop < nNodes; ++loop)
{
uroRTCClone.ClosedNodes.push(document.getElementsByClassName('fromNodeClosed')[loop].checked);
}
}
document.getElementsByClassName('closures')[0].getElementsByClassName('cancel-button')[0].click();
// auto-increment the start and end dates
uroRTCClone.StartDate = uroIncrementClosureDate(uroRTCClone.StartDate,uroRTCClone.PendingCloneIncrement);
uroRTCClone.EndDate = uroIncrementClosureDate(uroRTCClone.EndDate,uroRTCClone.PendingCloneIncrement);
uroRTCClone.PendingClone = -2;
},
Clone: function()
{
uroRTCClone.PendingCloneIncrement = parseInt(this.id.split('-')[1]);
uroRTCClone.PendingClone = parseInt(this.id.split('-')[2]);
},
GetDeletableClosures: function()
{
let dElms = [];
let entryElms = document.querySelector('.closures-list')?.querySelectorAll('wz-card');
if(entryElms !== undefined)
{
let nEntries = entryElms.length;
for(let i = 0; i < nEntries; ++i)
{
let iSrc = entryElms[i].querySelector('.closure-status-chip').imageSrc;
if((iSrc.indexOf('active') !== -1) || (iSrc.indexOf('not-started') !== -1))
{
dElms.push(entryElms[i]);
}
}
}
return dElms;
},
WaitForClosureDeletion: function()
{
let deleted = false;
let statusElm = document.querySelector('.closures-list')?.querySelector('.closures-status-message');
if(statusElm !== undefined)
{
let statusText = I18n.lookup('closures.success.delete');
if(statusElm.innerText === statusText)
{
deleted = true;
}
}
let nDCs = uroRTCClone.GetDeletableClosures().length;
if(nDCs > 0)
{
if(deleted === false)
{
window.setTimeout(uroRTCClone.WaitForClosureDeletion, 100);
}
else
{
uroRTCClone.DeleteNextOnList();
}
}
else
{
uroRTCClone.ConfirmDelete = true;
}
},
DeleteNextOnList: function()
{
let dElms = uroRTCClone.GetDeletableClosures();
let nClosures = dElms.length;
if(nClosures > 0)
{
let menuText = I18n.lookup('closures.delete');
let menuEntries = dElms[0].querySelectorAll('wz-menu-item');
for(let i = 0; i < menuEntries.length; ++i)
{
if(menuEntries[i].innerText === menuText)
{
menuEntries[i].click();
window.setTimeout(uroRTCClone.WaitForClosureDeletion, 100);
break;
}
}
}
else
{
uroRTCClone.ConfirmDelete = true;
}
},
DeleteAll: function()
{
uroRTCClone.ConfirmDelete = true;
uroAlertBox.Show("fa-warning", "URO+ Warning", I18n.lookup("closures.delete_confirm_no_reason")+' ('+I18n.lookup("closures.apply_to_all")+')', true, "Yes", "No", uroRTCClone.DeleteAllAction, null);
},
DeleteAllAction: function()
{
uroRTCClone.ConfirmDelete = false;
let dElms = uroRTCClone.GetDeletableClosures();
if(dElms.length > 0)
{
uroRTCClone.DeleteNextOnList();
}
else
{
uroRTCClone.ConfirmDelete = true;
}
}
};
const uroRTCs = // Other RTC related stuff...
{
rtcTotal : null,
rtcSegIDs : null,
prTO : null,
mteDropDown : null,
lhdArrows : false,
GetDuration: function(rcObj)
{
//// https://issuetracker.google.com/issues/478409089
let rcObj_notSDK = W.model.roadClosures.getObjectById(rcObj.id);
let duration = new Date(rcObj_notSDK.attributes.endDate) - new Date(rcObj_notSDK.attributes.startDate);
return Math.floor(duration / 86400000);
},
GetOffset: function(rcDate)
{
let dateObj = new Date(rcDate);
return (0 - uroUtils.DateToDays(dateObj));
},
GetOrigin: function(rcObj)
{
//// https://issuetracker.google.com/issues/478409089
let rcObj_notSDK = W.model.roadClosures.getObjectById(rcObj.id);
if(rcObj_notSDK !== undefined)
{
if(rcObj_notSDK.attributes.attributions === null)
{
if(rcObj_notSDK.attributes.createdBy == -5)
{
retval = uroEnums.TRTC.WAZEFEED;
}
else if((W.model.users.objects[rcObj_notSDK.attributes.createdBy] !== undefined) && (W.model.users.objects[rcObj_notSDK.attributes.createdBy].attributes.rank == 6))
{
retval = uroEnums.TRTC.WAZEOTHER;
}
else if((rcObj_notSDK.attributes.provider === 'Map Editors Community') || (rcObj_notSDK.attributes.closureType === 'SEGMENT'))
{
retval = uroEnums.TRTC.WME;
}
else
{
retval = uroEnums.TRTC.APP;
}
}
else
{
for(let i = 0; i < rcObj_notSDK.attributes.attributions.length; ++i)
{
let ch = rcObj_notSDK.attributes.attributions[i].channel;
let uc = rcObj_notSDK.attributes.attributions[i].userChannel;
if(ch === 'WME_COMMUNITY_EDITOR')
{
retval = uroEnums.TRTC.WME;
}
else if((ch === 'FEED') || (uc === 'SYSTEM'))
{
retval = uroEnums.TRTC.WAZEFEED;
}
else if(ch === 'WME_OPS_EDITOR')
{
retval = uroEnums.TRTC.WAZEOTHER;
}
else if(ch === 'WME_ROAD_CLOSURE_PROBLEM_IMPORT')
{
retval = uroEnums.TRTC.PROBLEM_IMPORT;
}
else if(ch === 'WME_PARTNER_EDITOR')
{
retval = uroEnums.TRTC.PARTNER_EDITOR;
}
else
{
uroDBG.Add('RTC with unknown attribution: ' + ch);
console.debug(rcObj_notSDK);
}
}
}
}
return retval;
},
GetState: function(rcObj)
{
let retval = uroEnums.SRTC.UNKNOWN;
let rcStatus = rcObj.status;
if(rcStatus !== undefined)
{
if(rcStatus === "ACTIVE")
{
retval = uroEnums.SRTC.ACTIVE;
}
else if(rcStatus === "NOT_STARTED")
{
retval = uroEnums.SRTC.FUTURE;
}
else if(rcStatus.indexOf("FINISHED") != -1)
{
retval = uroEnums.SRTC.EXPIRED;
}
// Haven't seen one of these yet, so assuming it should be treated
// the same as an expired closure...
else if(rcStatus === "SUSPENDED")
{
retval = uroEnums.SRTC.EXPIRED;
}
}
return retval;
},
GetStateText: function(rcObj)
{
let retval = "---";
let i18 = I18n.lookup("closures.statuses")[rcObj.status];
if(i18 !== undefined)
{
retval = i18;
}
return retval;
},
GetForSelectedSegment: function(segID)
{
let closureTypes = uroEnums.DRTC.NONE;
let RTCObjs = [];
let selectedSegs = [];
if(segID === null)
{
// segID should always be set to a valid segment ID if we're being called from the segment mouseover
// handler, so if it's null it implies we've instead been called from the closure panel handler where
// we might therefore be dealing with a multi-segment selection...
selection = sdk.Editing.getSelection();
if(selection.objectType === 'segment')
{
selectedSegs = selection.ids;
}
}
if((selectedSegs.length > 0) || (segID !== null))
{
let filterExpired = uroUtils.GetCBChecked('_cbHideExpiredPopupRTCs');
let filterCurrent = uroUtils.GetCBChecked('_cbHidePopupRTCs');
let filterFuture = uroUtils.GetCBChecked('_cbHideFuturePopupRTCs');
let filterUnknown = uroUtils.GetCBChecked('_cbHideUnknownPopupRTCs');
let rcObjs = sdk.DataModel.RoadClosures.getAll();
for(let roadClosure in rcObjs)
{
let rcObj = rcObjs[roadClosure];
let rcState = uroRTCs.GetState(rcObj);
let ignore = ((rcState === uroEnums.SRTC.EXPIRED) && (filterExpired === true));
ignore = ignore || ((rcState === uroEnums.SRTC.ACTIVE) && (filterCurrent === true));
ignore = ignore || ((rcState === uroEnums.SRTC.FUTURE) && (filterFuture === true));
ignore = ignore || ((rcState === uroEnums.SRTC.UNKNOWN) && (filterUnknown === true));
if(ignore === false)
{
rcObj.segIDs = [rcObj.segmentId]; // copy the segID property into an array so we can push extra segIDs into it later...
// set a direction value corresponding to the A-B or B-A setting - if we later end up combining an A-B and B-A closure
// into a two-way closure, we can then change the direction value to indicate this as well
if(rcObj.isForward === true)
{
rcObj.direction = uroEnums.DRTC.SEG_AB;
}
else
{
rcObj.direction = uroEnums.DRTC.SEG_BA;
}
// for each of the selected or moused-over segments, find all the closures which have matching segIDs
if(segID !== null)
{
if(rcObj.segmentId == segID)
{
RTCObjs.push(rcObj);
}
}
else
{
for(let i = 0; i < selectedSegs.length; ++i)
{
if(rcObj.segmentID == selectedSegs[i].id)
{
RTCObjs.push(rcObj);
break;
}
}
}
}
}
// RTCObjs now contains all of the segment closures relating to all of the segments of interest, so
// we can begin to organise them such that by the time we exit this function, the array will then contain
// an optimised list of closures that matches up to the list shown in the closure sidepanel, taking into
// account closures applying to all segments vs some, closures that can be merged into two-ways etc.
// first sort the closure by their start date, with a secondary sort by direction for those closures
// that have the same start date
RTCObjs = RTCObjs.sort(function(a,b)
{
if(a.startDate === b.startDate)
{
if(a.direction == uroEnums.DRTC.SEG_AB) return -1;
return 1;
}
if(a.startDate > b.startDate) return 1;
return -1;
});
// if we've got at least two closures in the sorted list, we then test adjacent list entries
// to see if they contain closure details which are identical except for their segment IDs, and
// combine them if so
if(RTCObjs.length > 1)
{
let i = 0;
while(i < (RTCObjs.length - 1))
{
if(
(RTCObjs[i].modificationData.createdBy == RTCObjs[i+1].modificationData.createdBy) &&
(RTCObjs[i].endDate == RTCObjs[i+1].endDate) &&
(RTCObjs[i].trafficEventId == RTCObjs[i+1].trafficEventId) &&
(RTCObjs[i].description == RTCObjs[i+1].description) &&
(RTCObjs[i].startDate == RTCObjs[i+1].startDate) &&
(RTCObjs[i].direction == RTCObjs[i+1].direction)
)
{
RTCObjs[i].segIDs.push(RTCObjs[i+1].segmentId);
RTCObjs.splice(i+1, 1);
}
else
{
++i;
}
}
}
// after that first trimming of the list, if there are still two or more entries then
// we perform a second pass, this time merging any adjacent entries which have the same
// segment IDs in their segIDs arrays - these are two-way closures applying to all those
// segments, and so we also change the direction value to indicate two-way vs A-B or B-A
if(RTCObjs.length > 1)
{
let i = 0;
while(i < (RTCObjs.length - 1))
{
if
(
(RTCObjs[i].segIDs.sort().join(',') == RTCObjs[i+1].segIDs.sort().join(',')) &&
(RTCObjs[i].modificationData.createdBy == RTCObjs[i+1].modificationData.createdBy) &&
(RTCObjs[i].endDate == RTCObjs[i+1].endDate) &&
(RTCObjs[i].trafficEventId == RTCObjs[i+1].trafficEventId) &&
(RTCObjs[i].description == RTCObjs[i+1].description) &&
(RTCObjs[i].startDate == RTCObjs[i+1].startDate)
)
{
RTCObjs[i].direction = uroEnums.DRTC.SEG_BI;
RTCObjs.splice(i+1, 1);
}
++i;
}
}
}
let RTTCObjs = [];
if(segID !== null)
{
// If we've been called from the segment popup handler, we now also check for any turn closures
// associated with this segment, so that their details can also be shown in the popup...
let turnClosures = sdk.DataModel.TurnClosures.getAll();
for(let tc in turnClosures)
{
let tcObj = turnClosures[tc];
if(tcObj.fromSegmentId === segID)
{
tcObj.direction = uroEnums.DRTC.TURN_OUT;
RTTCObjs.push(tcObj);
}
else if(tcObj.toSegmentId === segID)
{
tcObj.direction = uroEnums.DRTC.TURN_IN;
RTTCObjs.push(tcObj);
}
}
if(RTTCObjs.length > 1)
{
RTTCObjs = RTTCObjs.sort(function(a,b)
{
if(a.startDate === b.startDate)
{
if(a.direction == uroEnums.DRTC.TURN_OUT) return -1;
return 1;
}
if(a.startDate > b.startDate) return 1;
return -1;
});
}
}
uroRTCObjs = RTCObjs.concat(RTTCObjs);
for(let i = 0; i < uroRTCObjs.length; ++i)
{
closureTypes |= uroRTCObjs[i].direction;
}
// the closure list ordering at this point doesn't always match up to the order used by the closures panel when
// a mixture of "all segment" and "some segment" closures are present - need to work out what ordering rules
// WME is using here...
return closureTypes;
},
ScrollToEndOfClosures: function()
{
let cItems = document.querySelectorAll('.closure-item');
let nClosures = cItems.length;
let segIDs = uroUtils.GetSelectedSegmentIDs();
let sameSegs = false;
if((segIDs !== null) && (uroRTCs.rtcSegIDs !== null))
{
if(segIDs.length == uroRTCs.rtcSegIDs.length)
{
sameSegs = true;
for(let i = 0; i < segIDs.length; ++i)
{
if(segIDs[i] != uroRTCs.rtcSegIDs[i])
{
sameSegs = false;
break;
}
}
}
}
if(sameSegs === false)
{
uroRTCs.rtcTotal = null;
uroRTCs.rtcSegIDs = segIDs;
}
if(nClosures > uroRTCs.rtcTotal)
{
if(cItems[0].getBoundingClientRect().height != 0)
{
uroRTCs.rtcTotal = nClosures;
if(uroUtils.GetCBChecked('_cbAutoScrollClosureList') == true)
{
// Scroll to the end of the closure tab, as that's where the closure you're most likely to be cloning
// is located...
cItems[cItems.length - 1].scrollIntoView();
}
}
}
},
ClosureEditUIChanged: function()
{
let mteDropDown = document.getElementById('closure_eventId');
if(mteDropDown !== null)
{
// note: this also fires when the UI is closed, due to the change events triggered as its elements are removed
// prior to the tab itself closing...
let notReady = 0;
if(document.getElementById('closure_reason').shadowRoot.querySelector('input') === null) notReady += 1;
if(document.getElementById('closure_direction').shadowRoot.querySelector('.selected-value') === null) notReady += 2;
else if(document.getElementById('closure_direction').shadowRoot.querySelector('.selected-value').innerText === "") notReady += 4;
if(document.getElementById('closure_startDate') === null) notReady += 8;
if(document.getElementById('closure_endDate') === null) notReady += 16;
if(mteDropDown.shadowRoot.querySelector('.selected-value') === null) notReady += 32;
else if(mteDropDown.shadowRoot.querySelector('.selected-value').innerText === "") notReady += 64;
if(document.getElementById('closure_permanent').shadowRoot.querySelector('.wz-checkbox') === null) notReady += 128;
if(notReady === 0)
{
if(uroRTCClone.PendingClone === -3)
{
uroRTCClone.Complete();
}
else if(uroRTCClone.PendingClone !== -1)
{
uroRTCClone.Copy();
}
else
{
uroRTCs.FixMTEDropDown(mteDropDown);
}
}
}
else
{
if(uroRTCClone.PendingClone === -2)
{
// generate a click event on the Add a closure button to open up the closure editing UI, then
// wait for the UI to finish opening...
document.getElementsByClassName('add-closure-button')[0].click();
uroRTCClone.PendingClone = -3;
}
uroClosureListHandler();
}
},
PanelRendered: function()
{
uroRTCs.prTO = null;
uroRTCs.CompleteMTEDropDownFix();
},
FixMTEDropDown: function(mteDropDown)
{
// Auto-selects the "None" event within the MTE dropdown element passed into the function, to avoid the user having
// to manually select it - the only time you'd need to select something other than None is when you're assigning a
// closure to a MTE, at which point you need to manually select the appropriate event from the dropdown anyway, so
// pre-selecting None doesn't increase the workload for setting up a MTE-related closure, and it reduces the workload
// for setting up other closures...
//
// The only possible negative to this hack is that it means the user can set up a MTE-related closure without being
// reminded by WME to select the appropriate event from the list, because now that we're defaulting it to None, WME
// will allow the closure to be set without complaining that no event is set... But TBH, that's a small price to pay
// compared with the far, FAR, larger irritation of forcing users to always select None for all the closures that
// get added every single day without ever needing to be associated with a MTE - if a handful of closures end up being
// added with the user having forgotten to select the MTE, then no biggie. The closure will still at least have been
// added and its effect on routing around the event will therefore still be just as it would be if the MTE had been
// associated with the closure - the only difference is that if someone then bothers to look at the overview map of the
// MTE, they won't see that particular segment listed as a closure. I can live with the tiny risk of that causing any
// real problems, when weighed up against the millions of extra clicks saved through pre-selecting None...
// As the closure UI is rendered in such a way as to generate a myriad of change events, we can't simply use any one
// of those events as a way to indicate when the UI actually has finished rendering and it's therefore safe to start
// altering the MTE dropdown. In the absence of any other way to figure out when this can be done, we therefore use a
// simple delay-based approach...
//
// First store the reference to the MTE dropdown
uroRTCs.mteDropDown = mteDropDown;
// Cancel our previous timeout if it's still running - this means another change event has occurred on the closure panel
// within 250ms of the last one, so we don't want to let the timeout handler complete as we now know the panel is still
// in a state of flux...
if(uroRTCs.prTO !== null)
{
window.clearTimeout(uroRTCs.prTO);
}
// Set up a new 250ms timeout - this will only expire once all of the change events have taken place on the panel, and
// can therefore be used to call the one-time handling required to fix the MTE dropdown
uroRTCs.prTO = window.setTimeout(uroRTCs.PanelRendered, 250);
},
CompleteMTEDropDownFix: function()
{
// Make sure the closure event list is available, and that we haven't already messed with it.
if((uroRTCs.mteDropDown !== null) && (uroRTCs.mteDropDown.tag != "touchedByURO"))
{
// The event dropdown is now some byzantine piece of DOM manipulation to generate something which looks like a
// regular select list, but which can't be manipulated like one... The first gotcha is that the selected item
// exists only within a shadow DOM section within the dropdown rather than simply being part of the list from
// which we'd be able to read off its selected index. So to check whether or not the user has selected an
// event already, we need to drill down into this shadow DOM to get its text contents, and compare those against
// the I18n translation for the choose event text. What a palaver...
let shadowElm = uroRTCs.mteDropDown.shadowRoot.querySelectorAll('.selected-value')[0];
if(shadowElm !== undefined)
{
// Sometimes we get here before WME has finished rendering, so if the event text hasn't been set yet then we
// need to return false and let the caller deal with it...
let eventText = uroRTCs.mteDropDown.shadowRoot.querySelectorAll('.selected-value')[0].innerText;
if(eventText !== '')
{
if(eventText == I18n.lookup('closures.choose_event'))
{
// Having now established that, yes, the closure hasn't yet been associated with any event, it's surprisingly
// easy to change it to "None" - we just generate a click event on the first child element in the main DOM (not
// the shadow DOM this time), which replicates what the user would do to select None manually.
let returnFocusTo = document.activeElement;
uroRTCs.mteDropDown.children[0].click();
returnFocusTo.focus();
}
// Tag the event list to prevent further processing attempts whilst the edit UI for this closure remains open.
uroRTCs.mteDropDown.tag = "touchedByURO";
let closureReason = document.querySelector('#closure_reason');
if(closureReason !== undefined)
{
closureReason.click();
}
}
}
}
},
MarkerInfo: function(rtcID, isVisible)
{
let rtcModel = sdk.DataModel.RoadClosures.getById({roadClosureId: rtcID});
let mObj = sdk.Map.getFeatureDomElement({featureId: rtcID, layerName: "closures"});
this.isVisible = isVisible;
this.status = rtcModel.status;
this.segmentId = rtcModel.segmentId;
this.isAB = (rtcModel.isForward === true);
this.isBA = (rtcModel.isForward === false);
this.fwdAngle = null;
this.revAngle = null;
this.pos = {left: null, top: null};
if(mObj !== null)
{
// Default all RTC markers to hidden - any we want to keep visible will then be set
// as such once we've applied the latest filtering settings
mObj.style.visibility = "hidden";
let tStr = mObj.firstChild?.style?.transform;
// Check to see if the LHD arrow fix has already been applied by FUME, so we can
// avoid applying the -180 angle correction again here which would then undo the fix
// when we come to render our own markers...
let lhdFixApplied = (tStr.indexOf("scaleX(-1)") !== -1);
let angle = parseFloat(tStr.split('(')[1].split('deg')[0]);
if(lhdFixApplied === true)
{
angle -= 180.0;
}
if(rtcModel.isForward === true)
{
this.fwdAngle = angle % 360;
this.revAngle = (angle - 180.0) % 360;
}
else
{
this.revAngle = angle % 360;
this.fwdAngle = (angle - 180.0) % 360;
}
this.pos.left = mObj.style.left;
this.pos.top = mObj.style.top;
}
},
CheckAndAddRTCObj: function(mI, rO)
{
// First see if the segment this RTC object relates to is already listed in markerInfo
let idx;
let sameSeg = false;
for(idx = 0; idx < mI.length; ++idx)
{
if(mI[idx].segmentId === rO.segmentId)
{
sameSeg = true;
break;
}
}
if(sameSeg === true)
{
// Segment already listed, so now we need to see if the closure described by this
// RTC object ought to replace the one already listed. The intention here is that
// by the time we've checked all the RTCs, markerInfo will give us a clear
// indication as to whether or not we need to render ANY RTC marker for the segment
// and, if so, what type it should be and which directional arrows it requires
// If this object contains position or arrow rotation info, update our segment
// entry accordingly so we can make use of it later
if(rO.pos.left !== null)
{
mI[idx].pos = rO.pos;
}
if(rO.fwdAngle !== null)
{
mI[idx].fwdAngle = rO.fwdAngle;
}
if(rO.revAngle !== null)
{
mI[idx].revAngle = rO.revAngle;
}
// If the current RTC object is for an unfiltered closure of a higher priority
// than the one already in markerInfo, update the stored priority
if(rO.isVisible === true)
{
newStatus = null;
// Active always takes priority over everything else
if(rO.status === "ACTIVE")
{
newStatus = "ACTIVE";
}
// Future closures are second priority
else if((rO.status === "NOT_STARTED") && (mI[idx].status !== "ACTIVE"))
{
newStatus = "NOT_STARTED";
}
// Ended closures are at the bottom of the pile, so use only if we haven't
// already identified a higher priority RTC to show for this segment...
else if((rO.status !== null) && (mI[idx].status !== "ACTIVE") && (mI[idx].status !== "NOT_STARTED"))
{
newStatus = rO.status;
}
// If this object is the same or higher priority as the one already in markerInfo,
// update the priority of the latter, and also adjust the directionality tracking.
// If we're changing the marker priority, we need to first dump the existing
// tracking info so we don't end up showing the wrong directionality for the higher
// priority marker we are showing, whereas if the priority is the same then we OR
// the existing info with the new, to account for the way WME uses two seperate
// marker objects for bidirectional closures
if(newStatus !== null)
{
if(mI[idx].status !== newStatus)
{
mI[idx].isAB = false;
mI[idx].isBA = false;
}
mI[idx].status = newStatus;
mI[idx].isAB ||= rO.isAB;
mI[idx].isBA ||= rO.isBA;
}
}
// If any of the RTCs in a stack are visible, we need to keep the RTC marker
// visible, so logically-ORing the individual RTC visibility flags means we'd only
// hide the marker if we've filtered all of the RTCs for that segment
mI[idx].isVisible ||= rO.isVisible;
}
else
{
// Segment isn't already in markerInfo, so provided this is a visible RTC object, then
// we can simply use it to initialise the entry for the segment. If it's not visible,
// we still need to use it to initialise the entry, but we first need to make a few
// tweaks to prevent it affecting how the visible objects are handled
if(rO.isVisible === false)
{
rO.status = null;
}
mI.push(rO);
}
return mI;
},
GenerateArrowDiv: function(isFwd, mI)
{
let aElm = document.createElement('div');
aElm.className = rtcClasses.arrow.generic + " ";
if(isFwd === true)
{
aElm.className += rtcClasses.arrow.ab + " ";
}
else
{
aElm.className += rtcClasses.arrow.ba + " ";
}
if(mI.status === "ACTIVE")
{
aElm.className += rtcClasses.arrow.active;
}
else if(mI.status === "NOT_STARTED")
{
aElm.className += rtcClasses.arrow.notStarted;
}
else
{
aElm.className += rtcClasses.arrow.finished;
}
aElm.touchedByURO = true;
aElm.className += " uroRTCMarkers";
let transform = "rotate(";
let angle = null;
if(isFwd === true)
{
angle = mI.fwdAngle;
}
else
{
angle = mI.revAngle;
}
if(uroRTCs.lhdArrows === true)
{
angle += 180.0;
}
transform += angle + "deg)";
if(uroRTCs.lhdArrows === true)
{
transform += " scaleX(-1)";
}
aElm.style.transform = transform;
return aElm;
},
Filter: function()
{
let pmTStart = performance.now();
let pmFunction = "uroRTCs.Filter";
if(uroFilterPreamble() === false) return;
if(uroLayers.LayerVisible("closures") !== true) return;
let rtcFilters =
[
[false, false, false, false], // UNKNOWN
[false, false, false, false], // WME
[false, false, false, false], // WAZEFEED
[false, false, false, false], // WAZEOTHER
[false, false, false, false], // APP
[false, false, false, false], // PROBLEM_IMPORT
[false, false, false, false] // PARTNER_EDITOR
];
rtcFilters[uroEnums.TRTC.UNKNOWN][uroEnums.SRTC.UNKNOWN] = uroUtils.GetCBChecked('_cbHideUnknownUnknownRTCs');
rtcFilters[uroEnums.TRTC.UNKNOWN][uroEnums.SRTC.EXPIRED] = uroUtils.GetCBChecked('_cbHideExpiredUnknownRTCs');
rtcFilters[uroEnums.TRTC.UNKNOWN][uroEnums.SRTC.ACTIVE] = uroUtils.GetCBChecked('_cbHideUnknownRTCs');
rtcFilters[uroEnums.TRTC.UNKNOWN][uroEnums.SRTC.FUTURE] = uroUtils.GetCBChecked('_cbHideFutureUnknownRTCs');
rtcFilters[uroEnums.TRTC.WME][uroEnums.SRTC.UNKNOWN] = uroUtils.GetCBChecked('_cbHideUnknownEditorRTCs');
rtcFilters[uroEnums.TRTC.WME][uroEnums.SRTC.EXPIRED] = uroUtils.GetCBChecked('_cbHideExpiredEditorRTCs');
rtcFilters[uroEnums.TRTC.WME][uroEnums.SRTC.ACTIVE] = uroUtils.GetCBChecked('_cbHideEditorRTCs');
rtcFilters[uroEnums.TRTC.WME][uroEnums.SRTC.FUTURE] = uroUtils.GetCBChecked('_cbHideFutureEditorRTCs');
rtcFilters[uroEnums.TRTC.WAZEFEED][uroEnums.SRTC.UNKNOWN] = uroUtils.GetCBChecked('_cbHideUnknownWazeFeedRTCs');
rtcFilters[uroEnums.TRTC.WAZEFEED][uroEnums.SRTC.EXPIRED] = uroUtils.GetCBChecked('_cbHideExpiredWazeFeedRTCs');
rtcFilters[uroEnums.TRTC.WAZEFEED][uroEnums.SRTC.ACTIVE] = uroUtils.GetCBChecked('_cbHideWazeFeedRTCs');
rtcFilters[uroEnums.TRTC.WAZEFEED][uroEnums.SRTC.FUTURE] = uroUtils.GetCBChecked('_cbHideFutureWazeFeedRTCs');
rtcFilters[uroEnums.TRTC.WAZEOTHER][uroEnums.SRTC.UNKNOWN] = uroUtils.GetCBChecked('_cbHideUnknownWazeRTCs');
rtcFilters[uroEnums.TRTC.WAZEOTHER][uroEnums.SRTC.EXPIRED] = uroUtils.GetCBChecked('_cbHideExpiredWazeRTCs');
rtcFilters[uroEnums.TRTC.WAZEOTHER][uroEnums.SRTC.ACTIVE] = uroUtils.GetCBChecked('_cbHideWazeRTCs');
rtcFilters[uroEnums.TRTC.WAZEOTHER][uroEnums.SRTC.FUTURE] = uroUtils.GetCBChecked('_cbHideFutureWazeRTCs');
rtcFilters[uroEnums.TRTC.APP][uroEnums.SRTC.UNKNOWN] = uroUtils.GetCBChecked('_cbHideUnknownAppRTCs');
rtcFilters[uroEnums.TRTC.APP][uroEnums.SRTC.EXPIRED] = uroUtils.GetCBChecked('_cbHideExpiredAppRTCs');
rtcFilters[uroEnums.TRTC.APP][uroEnums.SRTC.ACTIVE] = uroUtils.GetCBChecked('_cbHideAppRTCs');
rtcFilters[uroEnums.TRTC.APP][uroEnums.SRTC.FUTURE] = uroUtils.GetCBChecked('_cbHideFutureAppRTCs');
rtcFilters[uroEnums.TRTC.PROBLEM_IMPORT][uroEnums.SRTC.UNKNOWN] = uroUtils.GetCBChecked('_cbHideUnknownProbImpRTCs');
rtcFilters[uroEnums.TRTC.PROBLEM_IMPORT][uroEnums.SRTC.EXPIRED] = uroUtils.GetCBChecked('_cbHideExpiredProbImpRTCs');
rtcFilters[uroEnums.TRTC.PROBLEM_IMPORT][uroEnums.SRTC.ACTIVE] = uroUtils.GetCBChecked('_cbHideProbImpRTCs');
rtcFilters[uroEnums.TRTC.PROBLEM_IMPORT][uroEnums.SRTC.FUTURE] = uroUtils.GetCBChecked('_cbHideFutureProbImpRTCs');
rtcFilters[uroEnums.TRTC.PARTNER_EDITOR][uroEnums.SRTC.UNKNOWN] = uroUtils.GetCBChecked('_cbHideUnknownPartEditRTCs');
rtcFilters[uroEnums.TRTC.PARTNER_EDITOR][uroEnums.SRTC.EXPIRED] = uroUtils.GetCBChecked('_cbHideExpiredPartEditRTCs');
rtcFilters[uroEnums.TRTC.PARTNER_EDITOR][uroEnums.SRTC.ACTIVE] = uroUtils.GetCBChecked('_cbHidePartEditRTCs');
rtcFilters[uroEnums.TRTC.PARTNER_EDITOR][uroEnums.SRTC.FUTURE] = uroUtils.GetCBChecked('_cbHideFuturePartEditRTCs');
let uFR_invertFilters = uroUtils.GetCBChecked('_cbInvertRTCFilters');
let uFR_filterShowForMTE = uroUtils.GetCBChecked('_cbShowMTERTCs');
let uFR_filterHideForMTE = uroUtils.GetCBChecked('_cbHideMTERTCs');
let uFR_filterHideDurationLessThan = uroUtils.GetCBChecked('_cbEnableRTCDurationFilterLessThan');
let uFR_filterHideDurationMoreThan = uroUtils.GetCBChecked('_cbEnableRTCDurationFilterMoreThan');
let uFR_thresholdDurationLessThan = uroUtils.GetElmValue('_inputFilterRTCDurationLessThan');
let uFR_thresholdDurationMoreThan = uroUtils.GetElmValue('_inputFilterRTCDurationMoreThan');
let uFR_filterShowForTS = uroUtils.GetCBChecked('_cbRTCFilterShowForTS');
let uFR_filterHideForTS = uroUtils.GetCBChecked('_cbRTCFilterHideForTS');
let tsD = uroUtils.GetElmValue('_inputRTCFilterDay');
let tsMo = uroUtils.GetElmValue('_inputRTCFilterMonth');
let tsY = uroUtils.GetElmValue('_inputRTCFilterYear');
let tsH = uroUtils.GetElmValue('_inputRTCFilterHour');
let tsMi = uroUtils.GetElmValue('_inputRTCFilterMin');
let filterTS = uroUtils.GetTS(tsD, tsMo, tsY, tsH, tsMi);
uroUpdateMTEList();
let mteID = null;
let selectorMTE = document.getElementById('_selectRTCMTE');
if(selectorMTE?.selectedOptions[0] != null)
{
mteID = selectorMTE.selectedOptions[0].value;
}
let uFR_masterEnable = uroUtils.IsFilteringEnabled(false);
let markerInfo = [];
let markerDiv = null;
// Build a list of all closures which, based on the filtering settings, need to
// be shown
let rtcObjs = sdk.DataModel.RoadClosures.getAll();
for (let r in rtcObjs)
{
let isVisible = true;
let rtcModel = rtcObjs[r];
if(uFR_masterEnable === true)
{
// First we delete any markers we'd generated on a previous filtering pass so we're starting
// with a clean slate
let myMarkers = document.querySelectorAll('.uroRTCMarkers');
for(let i = 0; i < myMarkers.length; ++i)
{
myMarkers[i].remove();
}
if(mteID !== null)
{
if((uFR_filterShowForMTE === true) && (rtcModel.trafficEventId !== mteID))
{
isVisible = false;
}
if((uFR_filterHideForMTE === true) && (rtcModel.trafficEventId === mteID))
{
isVisible = false;
}
}
let rtcType = uroRTCs.GetOrigin(rtcModel);
let rtcState = uroRTCs.GetState(rtcModel);
if(rtcFilters[rtcType][rtcState] === true)
{
isVisible = false;
}
if(uFR_invertFilters === true)
{
isVisible = !isVisible;
}
let rtcDuration = uroRTCs.GetDuration(rtcModel);
if(uFR_filterHideDurationLessThan === true)
{
if(rtcDuration < uFR_thresholdDurationLessThan)
{
isVisible = false;
}
}
if(uFR_filterHideDurationMoreThan === true)
{
if(rtcDuration > uFR_thresholdDurationMoreThan)
{
isVisible = false;
}
}
if((uFR_filterShowForTS === true) || (uFR_filterHideForTS === true))
{
let startTS = new Date(rtcModel.startDate).getTime();
let endTS = new Date(rtcModel.endDate).getTime();
if(uFR_filterShowForTS === true)
{
if((filterTS < startTS) || (filterTS > endTS))
{
isVisible = false;
}
}
if (uFR_filterHideForTS === true)
{
if((filterTS >= startTS) && (filterTS <= endTS))
{
isVisible = false;
}
}
}
}
let rtcObj = new uroRTCs.MarkerInfo(rtcModel.id, isVisible);
if((rtcObj.mObj !== null) && (markerDiv === null))
{
let mObj = sdk.Map.getFeatureDomElement({featureId: rtcModel.id, layerName: "closures"});
if(mObj !== null)
{
markerDiv = mObj.parentElement;
}
}
markerInfo = uroRTCs.CheckAndAddRTCObj(markerInfo, rtcObj);
}
////console.debug(rtcObjs);
////console.debug(markerInfo);
// markerInfo[] now contains all the information we need to be able to render the correct
// RTC marker and directionality arrows for all still-visible RTCs. As we've already hidden
// all of the native markers whilst building the markerInfo list, and as we can't rely on WME
// having rendered the correct directionality arrows for the marker we want to show on a
// segment, we need to generate our own markers...
if(markerDiv !== null)
{
let tc = sdk.DataModel.Countries.getTopCountry();
if(tc !== null)
{
uroRTCs.lhdArrows = tc.isLeftHandTraffic;
}
for(let i = 0; i < markerInfo.length; ++i)
{
if(markerInfo[i].isVisible === true)
{
let mElm = document.createElement('div');
mElm.className = rtcClasses.marker.prefix + " ";
if(markerInfo[i].status === "ACTIVE")
{
mElm.className += rtcClasses.marker.active;
}
else if(markerInfo[i].status === "NOT_STARTED")
{
mElm.className += rtcClasses.marker.notStarted;
}
else
{
mElm.className += rtcClasses.marker.finished;
}
mElm.className += " " + rtcClasses.marker.suffix;
mElm.className += " uroRTCMarkers";
mElm.style.left = markerInfo[i].pos.left;
mElm.style.top = markerInfo[i].pos.top;
mElm.style.position = "absolute";
mElm.touchedByURO = true;
if(markerInfo[i].isAB === true)
{
mElm.appendChild(uroRTCs.GenerateArrowDiv(true, markerInfo[i]));
}
if(markerInfo[i].isBA === true)
{
mElm.appendChild(uroRTCs.GenerateArrowDiv(false, markerInfo[i]));
}
markerDiv.appendChild(mElm);
}
}
}
uroDBG.PerfMon(pmFunction, pmTStart);
}
};
const uroIgnore = // ignore list
{
IsOnList: function(fid)
{
if(sessionStorage.UROverview_FID_IgnoreList.indexOf('fid:'+fid) == -1) return false;
else return true;
},
EnableControls: function()
{
let btnState = "visible";
if(sessionStorage.UROverview_FID_IgnoreList === '')
{
btnState = "hidden";
}
try
{
document.getElementById('_btnUndoLastHide').style.visibility = btnState;
document.getElementById('_btnClearSessionHides').style.visibility = btnState;
uroFilterItems();
}
catch(err)
{
uroDBG.AddLog('exception thrown in uroIgnore.EnableControls()');
}
},
Add: function()
{
if(!uroIgnore.IsOnList(uroShownFID))
{
sessionStorage.UROverview_FID_IgnoreList += 'fid:'+uroShownFID;
uroDBG.AddLog('added fid '+uroShownFID+' to ignore list');
uroDBG.AddLog(sessionStorage.UROverview_FID_IgnoreList);
uroDiv.style.visibility = 'hidden';
uroIgnore.EnableControls();
W.map.events.register("mousemove", null, uroFilterItemsOnMove);
}
return false;
},
RemoveLastAdded: function()
{
let ignorelist = sessionStorage.UROverview_FID_IgnoreList;
let fidpos = ignorelist.lastIndexOf('fid:');
if(fidpos != -1)
{
ignorelist = ignorelist.slice(0,fidpos);
sessionStorage.UROverview_FID_IgnoreList = ignorelist;
uroDBG.AddLog('removed last fid from ignore list');
uroDBG.AddLog(sessionStorage.UROverview_FID_IgnoreList);
uroIgnore.EnableControls();
}
},
RemoveAll: function()
{
sessionStorage.UROverview_FID_IgnoreList = '';
uroIgnore.EnableControls();
}
};
const uroPopup = // map object popup handling
{
hasIgnoreLink : null,
hasDeleteLink : null,
hasAddWatchLink : null,
hasRemoveWatchLink : null,
hasUpdateWatchLink : null,
hasOpenInNewTabLink : null,
isVenue : null,
isMapComment : null,
isUR : null,
isProblem : null,
isTurnProb : null,
isPlaceUpdate : null,
timer : -2,
autoHideTimer : 0,
mouseIn : false,
shown : false,
shownType : null,
newType : null,
hovered : null,
unstackedX : null,
unstackedY : null,
renderIntent : null,
pX : null,
pY : null,
suppressed : false,
GetFormattedLocks: function(attrs)
{
let autoLock = attrs.rank;
let userLock = attrs.lockRank;
let retval = '<b>' + I18n.lookup("edit.segment.fields.lock") + ': </b>';
if(userLock !== null)
{
retval += 'M' + (userLock+1) + ' / ';
}
retval += 'A' + (autoLock+1);
return retval;
},
UR: function()
{
let result = '';
// check for stacking...
if(uroShownFID != uroMarkers.id)
{
uroCheckStacking(uroLayersFT.ID.UR,uroMarkers.id);
}
if(uroUtils.GetCBChecked('_cbInhibitURPopup') === false)
{
if(uroMousedOverMapComment !== null)
{
uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for UR highlight');
uroMousedOverOtherObjectWithinMapComment = true;
}
uroPopup.isUR = true;
uroPopup.newPopupType = uroMarkers.type;
let ureq = W.model.mapUpdateRequests.objects[uroMarkers.id];
if(ureq.attributes != undefined)
{
uroFID = uroMarkers.id;
uroDBG.AddLog('building popup for UR '+uroMarkers.id);
result = '<b>Update Request ('+uroMarkers.id+'): ' + I18n.lookup("update_requests.types." + ureq.attributes.type) + '</b><br>';
result += uroUtils.Clickify(ureq.attributes.description, '<br>');
let uroDaysOld = uroUtils.GetURAge(ureq,0,false);
let uroSubmittedTS = uroUtils.GetURAge(ureq,0,true);
if(uroSubmittedTS != -1)
{
uroSubmittedTS = uroUtils.GetDateTimeString(uroSubmittedTS);
}
if(uroDaysOld != -1)
{
result += '<i>Submitted ' + uroUtils.ParseDaysAgo(uroDaysOld) + ' ';
if(uroSubmittedTS != -1) result += '(' + uroSubmittedTS + ') ';
if(ureq.attributes.guestUserName != null)
{
result += 'via Livemap';
if(ureq.attributes.guestUserName !== '')
{
result += ' by '+ureq.attributes.guestUserName.replace(/<\/?[^>]+(>|$)/g, "");
}
}
result += '</i>';
}
if(ureq.attributes.resolvedOn !== null)
{
let daysResolved = uroUtils.GetURAge(ureq,1,false);
let uroResolvedTS = uroUtils.GetURAge(ureq,1,true);
if(uroResolvedTS != -1)
{
uroResolvedTS = uroUtils.GetDateTimeString(uroResolvedTS);
}
if(daysResolved != -1)
{
result += '<br><i>Closed ' + uroUtils.ParseDaysAgo(daysResolved) + ' ';
if(uroResolvedTS != -1) result += '(' + uroResolvedTS + ')</i>';
result += '<br><i>Marked as ';
if(ureq.attributes.resolution === 0) result += 'solved';
else if(ureq.attributes.resolution == 1) result += 'not identified';
else result += 'unknown';
if(ureq.attributes.resolvedBy !== null)
{
result += ' by '+uroUtils.GetUserNameAndRank(ureq.attributes.resolvedBy);
}
result += '</i>';
}
}
let sdObj = uroURSessions.GetSessionData(uroMarkers.id);
if(sdObj != null)
{
let hasMyComments = uroURHasMyComments(uroMarkers.id);
let nComments = sdObj.comments.length;
result += '<br>' + nComments + ' comment';
if(nComments != 1) result += 's';
if((hasMyComments === false) && (nComments > 0)) result += ' (none by me)';
if(nComments > 0)
{
let commentDaysOld = uroUtils.GetCommentAge(sdObj.comments[nComments-1]);
if(commentDaysOld != -1)
{
result += ', last update '+uroUtils.ParseDaysAgo(commentDaysOld);
}
}
}
if(uroURDupes.length > 0)
{
let thisID = parseInt(uroMarkers.id);
for(let i = 0; i < uroURDupes.length; ++i)
{
if(uroURDupes[i][0] === thisID)
{
result += '<br><br>Duplicate of: ';
let dupes = 0;
for(let j = 0; j < uroURDupes[i][1].length; ++j)
{
if(uroURDupes[i][1][j] !== thisID)
{
if(dupes > 0)
{
result += ', ';
}
result += uroURDupes[i][1][j];
++dupes;
}
}
}
}
}
uroPopup.result += result;
uroPopup.UMPExtras(ureq);
uroPopup.Show();
}
}
},
MP: function()
{
let result = '';
// check for stacking...
if(uroShownFID != uroMarkers.id)
{
uroCheckStacking(uroLayersFT.ID.MP,uroMarkers.id);
}
if(uroUtils.GetCBChecked('_cbInhibitMPPopup') === false)
{
if(uroMousedOverMapComment !== null)
{
uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for MP highlight');
uroMousedOverOtherObjectWithinMapComment = true;
}
uroPopup.isProblem = true;
uroPopup.newPopupType = uroMarkers.type;
let ureq = W.model.mapProblems.objects[uroMarkers.id];
uroFID = uroMarkers.id;
uroDBG.AddLog('building popup for problem '+uroMarkers.id);
if(uroPopup.isTurnProb) result = '<b>Turn Problem ('+uroMarkers.id+'): ' + I18n.lookup("problems.types.turn.title");
else
{
result = '<b>Map Problem ('+uroMarkers.id+'): ';
let problemType = ureq.attributes.subType;
if(problemType == 300)
{
result += I18n.lookup("problems.panel.closure.title");
}
else
{
if(I18n.lookup("problems.types." + problemType) === undefined) result += 'Unknown problem type ('+problemType+')';
else result += I18n.lookup("problems.types." + problemType + ".title");
}
}
result += '</b><br>';
if(ureq.attributes.description != null)
{
result += 'Description: ' + ureq.attributes.description + '<br>';
}
if(ureq.attributes.extraInfo != null)
{
result += 'ExtraInfo: ' + uroUtils.Clickify(ureq.attributes.extraInfo, '<br>');
}
if(ureq.attributes.provider != null)
{
result += 'Provider: ' + ureq.attributes.provider + '<br>';
}
if(ureq.attributes.startTime != null)
{
result += 'From: ' + uroUtils.GetDateTimeString(ureq.attributes.startTime) + '<br>';
}
if(ureq.attributes.endTime != null)
{
result += 'To: ' + uroUtils.GetDateTimeString(ureq.attributes.endTime) + '<br>';
}
if(ureq.attributes.resolvedOn != null)
{
let daysResolved = uroUtils.GetURAge(ureq,1,false);
if(daysResolved != -1)
{
result += '<br><i>Closed ' + uroUtils.ParseDaysAgo(daysResolved) + ' ';
if(ureq.attributes.resolvedBy != null)
{
result += ' by '+uroUtils.GetUserNameAndRank(ureq.attributes.resolvedBy);
}
if((ureq.attributes.open === true) && (ureq.attributes.resolvedOn != null))
{
result += '<br>Reopened by Waze';
}
result += '</i>';
}
}
uroPopup.result += result;
uroPopup.UMPExtras(ureq);
}
},
PUR: function()
{
let result = '';
// check for stacking...
if(uroShownFID != uroMarkers.id)
{
uroCheckStacking(uroMarkers.type, uroMarkers.id);
}
if(uroUtils.GetCBChecked('_cbInhibitPUPopup') === false)
{
let ureq = sdk.DataModel.Venues.getById({venueId: uroMarkers.id});
uroPopup.newPopupType = uroMarkers.type;
if(uroMousedOverMapComment !== null)
{
uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for PUR highlight');
uroMousedOverOtherObjectWithinMapComment = true;
}
uroPopup.isPlaceUpdate = true;
uroFID = uroMarkers.id;
if(uroMarkers.type == uroLayersFT.ID.VPUR)
{
uroDBG.AddLog('building popup for placeUpdate '+uroMarkers.id);
}
result = '<b>';
if(ureq.name === '') result += 'Unnamed landmark';
else result += ureq.name;
result += '</b><br>';
result += '<ul>';
for(let idx = 0; idx < ureq.categories.length; idx++)
{
result += '<li>' + I18n.lookup("venues.categories." + ureq.categories[idx]);
}
result += '</ul>';
if(ureq.isResidential === true)
{
result += '<i>Residential</i>';
}
let daysOld = uroUtils.GetPURAge(ureq);
if(daysOld != -1)
{
result += '<br><i>Submitted '+uroUtils.ParseDaysAgo(daysOld)+'</i>';
}
uroPopup.result += result;
uroPopup.UMPExtras(ureq);
}
},
Venue: function()
{
if(uroUtils.GetCBChecked('_cbInhibitLandmarkPopup') === false)
{
let result = '';
let navpointPos=new OpenLayers.LonLat();
{
if(uroPopup.renderIntent === 'highlight')
{
if(uroUtils.ExtentContains(uroMarkers.obj.geometry) === true)
{
if(uroMousedOverMapComment !== null)
{
uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for place highlight');
uroMousedOverOtherObjectWithinMapComment = true;
}
if(uroPopup.newPopupType === null)
{
uroFID = uroMarkers.obj.id;
uroDBG.AddLog('building popup for place '+uroFID);
navpointPos = uroGetVenueNavPoint(uroFID);
result += '<b>';
if(uroMarkers.obj.name === '')
{
if(uroMarkers.obj.isResidential === true) result += '<i>Residential</i>';
else result += '<i>Unnamed</i>';
}
else result += uroUtils.Clickify(uroMarkers.obj.name, '');
if(uroMarkers.obj.externalProviderIds.length > 0)
{
result += ' <i>(linked)</i>';
}
if(uroMarkers.obj.isAdLocked)
{
result += ' <i>(AdLocked)</i>';
}
result += '</b><br>';
if(uroMarkers.obj.brand !== null)
{
result += '<i>Brand: ' + uroMarkers.obj.brand + '</i><br>';
}
let vDesc = uroMarkers.obj.description;
if(vDesc !== '')
{
result += '"<i>' + uroUtils.Clickify(vDesc, '') + '</i>"<br>';
}
let userLock = uroMarkers.obj.lockRank;
result += '<b>Lock: </b>' + (userLock+1);
result += '<hr>';
let venueAddr = sdk.DataModel.Venues.getAddress({venueId: uroFID});
result += uroGetAddress(venueAddr.street.id, venueAddr.houseNumber, false, false);
result += '<ul>';
for(let idx = 0; idx < uroMarkers.obj.categories.length; idx++)
{
result += '<li>' + I18n.lookup("venues.categories." + uroMarkers.obj.categories[idx]);
}
result += '</ul>';
let npLink = document.location.href;
let npLayers = '';
npLink = npLink.substring(0, npLink.indexOf('?zoomLevel'));
npLink += '?zoomLevel=17&lat='+navpointPos.lat+'&lon='+navpointPos.lon+npLayers;
let targetTab = "_uroTab_" + Math.round(Math.random()*1000000);
result += '<hr>Jump to nav point: <a href="'+npLink+'" id="_openInNewTab" target="'+targetTab+'">in new tab</a>';
uroPopup.hasOpenInNewTabLink = true;
uroPopup.newPopupType = 'venue';
uroPopup.isVenue = true;
uroPopup.result += result;
uroPopup.Show();
}
else
{
let otherID = uroMarkers.obj.id;
uroDBG.AddLog('venue '+otherID+' is also highlighted');
}
}
else
{
uroDBG.AddLog('landmark '+uroFID+' has renderIntent==highlight but is offscreen... blocking popup');
}
}
}
}
},
Comment: function()
{
if(uroUtils.GetCBChecked('_cbInhibitMapCommentPopup') === false)
{
if(uroMCLayer.name !== 'mapComments')
{
uroInit.WazeBits();
}
if(uroMCLayer !== null)
{
let result = '';
if(uroPopup.renderIntent == 'highlight')
{
let moAttrs = uroMarkers.obj;
if(uroUtils.ExtentContains(uroMarkers.obj.geometry) === true)
{
if(uroPopup.newPopupType === null)
{
if((uroMousedOverMapComment === moAttrs.id) && (uroMousedOverOtherObjectWithinMapComment === true))
{
uroDBG.AddLog('inhibit popup for map comment '+uroMousedOverMapComment);
}
else
{
uroMousedOverOtherObjectWithinMapComment = false;
if(moAttrs.isPoint === true)
{
// only capture ID for area comments...
uroMousedOverMapComment = moAttrs.id;
}
uroFID = moAttrs.id;
uroDBG.AddLog('building popup for map comment '+uroFID);
result += '<b>';
if(moAttrs.subject === '')
{
result += '<i>No subject</i>';
}
else result += uroUtils.Clickify(moAttrs.subject, '');
result += '</b><br>';
result += uroUtils.Clickify(moAttrs.body, '<br>');
let mcAgeData = uroUtils.GetMCAge(moAttrs.modificationData, 0);
if(mcAgeData.raw != null)
{
mcAgeData.raw = uroUtils.GetDateTimeString(mcAgeData.raw);
}
if(mcAgeData.days != null)
{
result += '<i>Submitted ' + uroUtils.ParseDaysAgo(mcAgeData.days) + ' ';
if(mcAgeData.raw != null) result += '(' + mcAgeData.raw + ') ';
if(moAttrs.modificationData.createdBy != null)
{
result += ' by '+uroUtils.GetUserNameAndRank(moAttrs.modificationData.createdBy);
}
result += '</i><br>';
}
mcAgeData = uroUtils.GetMCAge(moAttrs.modificationData, 1);
if(mcAgeData.raw != null)
{
mcAgeData.raw = uroUtils.GetDateTimeString(mcAgeData.raw);
}
if(mcAgeData.days != null)
{
result += '<i>Updated ' + uroUtils.ParseDaysAgo(mcAgeData.days) + ' ';
if(mcAgeData.raw != null) result += '(' + mcAgeData.raw + ') ';
if(moAttrs.modificationData.createdBy != null)
{
result += ' by '+uroUtils.GetUserNameAndRank(moAttrs.modificationData.updatedBy);
}
result += '</i><br>';
}
mcAgeData = uroUtils.GetMCAge(moAttrs, 2);
if(mcAgeData.days != null)
{
result += '<i>Expires ' + uroUtils.ParseDaysToGo(mcAgeData.days) + ' ';
result += '(' + uroUtils.GetDateTimeString(mcAgeData.raw) +')</i><br>';
}
let mcHasMyComments = false;
let mcNComments = moAttrs.conversation.length;
if(mcNComments > 0)
{
for(let j=0; j<mcNComments; j++)
{
if(moAttrs.conversation[j].userName == uroUserName)
{
mcHasMyComments = true;
break;
}
}
}
result += '<br>' + mcNComments +' comment';
if(mcNComments != 1) result += 's';
if((mcHasMyComments === false) && (mcNComments > 0)) result += ' (none by me)';
// add "ignore for this session" link
result += '<br><a href="#" id="_addtoignore">Hide for this session</a>';
uroPopup.hasIgnoreLink = true;
uroPopup.newPopupType = 'map_comment';
uroPopup.isMapComment = true;
uroPopup.result += result;
uroPopup.Show();
}
}
else
{
let mcOtherID = moAttrs.id;
uroDBG.AddLog('map comment '+mcOtherID+' is also highlighted');
}
}
else
{
uroDBG.AddLog('map comment '+uroFID+' has renderIntent==highlight but is offscreen... blocking popup');
}
}
}
}
},
AddClosureRow: function(rcObj)
{
let startDate = rcObj.startDate;
let endDate = "unknown";
if(rcObj.endDate !== null)
{
endDate = rcObj.endDate;
}
let provider = "---";
//// https://issuetracker.google.com/issues/473096295
let rcObj_notSDK = W.model.roadClosures.getObjectById(rcObj.id);
if(rcObj_notSDK.attributes.provider !== null)
{
provider = rcObj_notSDK.attributes.provider;
}
else if(rcObj.modificationData.createdBy !== null)
{
provider = uroUtils.GetUserNameAndRank(rcObj.modificationData.createdBy);
}
let reason = "---";
if(rcObj.description !== null)
{
reason = rcObj.description;
}
let mte = "---";
//// https://issuetracker.google.com/issues/540020379
let mteID = null;
if(rcObj.majorTrafficEventId !== undefined)
{
mteID = rcObj.majorTrafficEventId;
}
else
{
mteID = rcObj.trafficEventId;
}
if(mteID !== null)
{
let mteObj = sdk.DataModel.MajorTrafficEvents.getById({majorTrafficEventId: mteID});
if(mteObj !== null)
{
mte = mteObj.names[0].value;
}
}
let startOffset = uroRTCs.GetOffset(rcObj.startDate);
let duration = uroRTCs.GetDuration(rcObj);
let durationStr = '';
if(duration > 1)
{
durationStr = I18n.lookup("datetime.distance_in_words.x_days").other;
durationStr = durationStr.replace("%{count}", duration);
}
else
{
durationStr = I18n.lookup("datetime.distance_in_words.x_days").one;
if(duration === 0)
{
durationStr = "<" + durationStr;
}
}
let state = uroRTCs.GetState(rcObj);
let status = uroRTCs.GetStateText(rcObj);
let bgCol = '';
if(state === uroEnums.SRTC.EXPIRED)
{
bgCol = '#A0A0A0';
}
else if(state === uroEnums.SRTC.ACTIVE)
{
bgCol = '#FFFFFF';
}
else if(state === uroEnums.SRTC.FUTURE)
{
// For future closures, override the default status text with an indication
// of how many days until the start of the closure
if(startOffset == 0)
{
status = I18n.lookup("date.today");
status = status.charAt(0).toUpperCase() + status.slice(1);
}
else if(startOffset == 1)
{
status = I18n.lookup("datetime.distance_in_words.x_days").one;
}
else
{
status = I18n.lookup("datetime.distance_in_words.x_days").other;
status = status.replace("%{count}", startOffset);
}
bgCol = '#C0C0C0';
}
let borderStyle = 'border:1px solid #888;';
let tdTag = '<td nowrap style="' + borderStyle + '">';
let result = '';
result += '<tr bgcolor="' + bgCol + '">';
result += tdTag + status + '</td>';
result += tdTag + startDate + ' to ' + endDate + ' (' + durationStr + ')</td>';
result += tdTag + provider + '</td>';
result += tdTag + reason + '</td>';
result += tdTag + mte + '</td>';
result += '</td></tr>';
return result;
},
AddClosureDetails: function(cTypes, addType, addDesc)
{
let retval = '';
if((cTypes & addType) === addType)
{
retval += '<tr><td colspan=4><b>' + addDesc + ':</b></td></tr>';
for(let closure in uroRTCObjs)
{
if(uroRTCObjs.hasOwnProperty(closure))
{
let cObj = uroRTCObjs[closure];
if(cObj.direction === addType)
{
retval += uroPopup.AddClosureRow(cObj);
}
}
}
}
return retval;
},
Segment: function()
{
let borderStyle = 'border:1px solid #888;';
if(uroUtils.GetCBChecked('_cbInhibitSegPopup') === false)
{
{
if(uroUtils.ExtentContains(uroMarkers.obj.geometry) === true)
{
let result = '';
let doPopUp = false;
let restObj;
if(uroMousedOverMapComment !== null)
{
uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for segment highlight');
uroMousedOverOtherObjectWithinMapComment = true;
}
let segID = uroMarkers.obj.id;
if(segID !== null)
{
// generic segment data
if(uroUtils.GetCBChecked('_cbInhibitSegGenericPopup') === false)
{
doPopUp = true;
uroDBG.AddLog('building popup for segment '+segID);
let isToll = sdk.DataModel.Segments.isTollSegment({segmentId: segID});
let streetID = uroMarkers.obj.primaryStreetId;
result += uroGetAddress(streetID, null, true, isToll);
if(uroMarkers.obj.alternateStreetIds.length > 0)
{
// list any alternate names
result += '<br>Alternate names:<br>';
for(let i = 0; i < uroMarkers.obj.alternateStreetIds.length; ++i)
{
let streetObj = sdk.DataModel.Streets.getById({streetId: uroMarkers.obj.alternateStreetIds[i]});
if(streetObj !== null)
{
result += ' <i>' + streetObj.name + ', ';
let cityName = "";
let cityObj = sdk.DataModel.Cities.getById({cityId: streetObj.cityId});
if(cityObj !== null)
{
cityName = cityObj.name;
}
if(cityName != "")
{
result += cityName;
}
else
{
result += ' no city';
}
result += '</i><br>';
}
}
result += '<br>';
}
result += '<b>ID: </b>'+uroMarkers.obj.id+' - ';
result += uroPopup.GetFormattedLocks(uroMarkers.obj);
result += ' - ';
let level = uroMarkers.obj.elevationLevel;
result += '<b>' + I18n.lookup("edit.segment.fields.level") +': </b>';
if(level == 0)
{
result += I18n.lookup("edit.segment.levels")[0];
}
else
{
result += level;
}
result += '<br>';
let leBy = uroMarkers.obj.modificationData.updatedBy;
let leOn = uroMarkers.obj.modificationData.updatedOn;
if(leOn == null)
{
leBy = uroMarkers.obj.modificationData.createdBy;
leOn = uroMarkers.obj.modificationData.createdOn;
}
result += "<b>Last edit by</b> " + uroUtils.GetUserNameAndRank(leBy) + " <b>on</b> " + uroUtils.GetDateTimeString(leOn) + "<br><br>";
let fwdSpeed = uroMarkers.obj.fwdSpeedLimit;
let revSpeed = uroMarkers.obj.revSpeedLimit;
let fwdLanes = 'Not set';
let revLanes = 'Not set';
let fwdWidth = 'Not set';
let revWidth = 'Not set';
if(uroMarkers.obj.fromLanesInfo != null)
{
fwdLanes = uroMarkers.obj.fromLanesInfo.numberOfLanes;
fwdWidth = uroUtils.GetLengthString(uroMarkers.obj.fromLanesInfo.laneWidth);
}
if(uroMarkers.obj.toLanesInfo != null)
{
revLanes = uroMarkers.obj.toLanesInfo.numberOfLanes;
revWidth = uroUtils.GetLengthString(uroMarkers.obj.toLanesInfo.laneWidth);
}
let fwdUnverified = uroMarkers.obj.isFwdSpeedLimitUnverified;
let revUnverified = uroMarkers.obj.isRevSpeedLimitUnverified;
let fwdASC = (uroMarkers.obj.flagAttributes.fwdSpeedCamera === true);
let revASC = (uroMarkers.obj.flagAttributes.revSpeedCamera === true);
let thTag = '<th style="' + borderStyle + '">';
let tdTag = '<td style="' + borderStyle + '">';
result += '<table border=1>';
result += '<tr>';
result += thTag + 'Dir</th>';
result += thTag + 'Speed</th>';
result += thTag + 'ASC</th>';
result += thTag + 'Lanes</th>';
result += thTag + 'Width</th>';
result += '</tr>';
if(uroMarkers.obj.isAtoB || uroMarkers.obj.isTwoWay)
{
result += '<tr>' + tdTag + '<b>A-B</b></td>';
result += tdTag + uroUtils.GetLocalisedSpeedString(fwdSpeed) + '</td>';
result += tdTag;
if(fwdASC == true)
{
result += 'Yes';
}
else
{
result += 'No';
}
result += '</td>';
result += tdTag;
if(fwdLanes > 0)
{
result += fwdLanes;
}
else
{
result += 'Not set';
}
result += '</td>';
result += tdTag + fwdWidth + '</td></tr>';
}
if(uroMarkers.obj.isBtoA || uroMarkers.obj.isTwoWay)
{
result += '<tr>' + tdTag + '<b>B-A</b></td>';
result += tdTag + uroUtils.GetLocalisedSpeedString(revSpeed) + '</td>';
result += tdTag;
if(revASC == true)
{
result += 'Yes';
}
else
{
result += 'No';
}
result += '</td>';
result += tdTag;
if(revLanes > 0)
{
result += revLanes;
}
else
{
result += 'Not set';
}
result += '</td>';
result += tdTag + revWidth + '</td></tr>';
}
result += '</table>';
if((uroMarkers.obj.isTwoWay) && (fwdSpeed != revSpeed) && (!fwdUnverified) && (!revUnverified))
{
result += '<br>Two-way segment has different verified speed limits...';
}
}
// segment restrictions
if(uroMarkers.obj.restrictions.length > 0)
{
let tdTag = '<td colspan=13 style="' + borderStyle + '">';
result += '<br><table border=1>';
doPopUp = true;
let fwdResult = '<tr>' + tdTag + '<b>A-B restrictions:</b></td></tr>';
let revResult = '<tr>' + tdTag + '<b>B-A restrictions:</b></td></tr>';
let bothResult = '<tr>' + tdTag + '<b>Two-way restrictions:</b></td></tr>';
let nABRestrictions = 0;
let nBARestrictions = 0;
let nBothRestrictions = 0;
//// https://issuetracker.google.com/issues/448433097
let tRestrictions = W.model.segments.objects[segID].attributes.restrictions;
for(let idx = 0; idx < tRestrictions.length; idx++)
{
restObj = tRestrictions[idx];
if(restObj._direction === "FWD")
{
nABRestrictions++;
fwdResult += uroFormatRestriction(restObj, borderStyle);
}
else if(restObj._direction === "REV")
{
nBARestrictions++;
revResult += uroFormatRestriction(restObj, borderStyle);
}
else if(restObj._direction === "BOTH")
{
nBothRestrictions++;
bothResult += uroFormatRestriction(restObj, borderStyle);
}
else
{
uroDBG.AddLog("unknown restriction direction...");
}
}
if(nABRestrictions > 0)
{
result += fwdResult;
}
if(nBARestrictions > 0)
{
result += revResult;
}
if(nBothRestrictions > 0)
{
result += bothResult;
}
result += '</table>';
}
if(sdk.Map.isLayerVisible({layerName: uroLayers.layers[uroLayersFT.ID.RTC].name}) === true)
{
let closureTypes = uroRTCs.GetForSelectedSegment(uroMarkers.obj.id);
if(closureTypes !== uroEnums.DRTC.NONE)
{
result += '<br><table border=1 width="100%">';
result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.SEG_AB, "A-B closures");
result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.SEG_BA, "B-A closures");
result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.SEG_BI, "Two-way closures");
result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.TURN_OUT, "Outbound turn closures");
result += uroPopup.AddClosureDetails(closureTypes, uroEnums.DRTC.TURN_IN, "Inbound turn closures");
doPopUp = true;
result += '</table>';
}
}
if(doPopUp === true)
{
uroFID = uroMarkers.obj.id;
uroPopup.newPopupType = 'segment_restriction';
}
}
uroPopup.result += result;
uroPopup.Show();
}
else
{
uroDBG.AddLog('segment '+uroFID+' has renderIntent==highlight but is offscreen... blocking popup');
}
}
}
},
Node: function()
{
if(uroUtils.GetCBChecked('_cbInhibitNodesPopup') === false)
{
let result = '';
let nodeObj = sdk.DataModel.Nodes.getById({nodeId: uroMarkers.id});
if(nodeObj === null)
{
uroMarkers.id = null;
}
else
{
if(uroMousedOverMapComment !== null)
{
uroDBG.AddLog('setting uroMousedOverOtherObjectWithinMapComment for node highlight');
uroMousedOverOtherObjectWithinMapComment = true;
}
uroPopup.newPopupType = 'node';
uroFID = uroMarkers.id;
uroDBG.AddLog('building popup for node '+uroFID);
result += '<b>Node: ' + uroFID + '</b><br>';
let nodeSegRTCs = [];
for(let k=0; k < nodeObj.connectedSegmentIds.length; k++)
{
let nodeSegID = nodeObj.connectedSegmentIds[k];
let nodeSegObj = sdk.DataModel.Segments.getById({segmentId: nodeSegID});
if(nodeSegObj !== null)
{
let nodeStreetID = nodeSegObj.primaryStreetId;
result += uroGetAddress(nodeStreetID, null, false, false);
}
uroRTCs.GetForSelectedSegment(nodeSegID);
nodeSegRTCs = nodeSegRTCs.concat(uroRTCObjs);
}
}
uroPopup.result += result;
uroPopup.Show();
}
},
UMPExtras: function(ureq)
{
// add "open new WME tab" link
let urPos=new OpenLayers.LonLat();
if(uroPopup.isPlaceUpdate)
{
urPos.lon = ureq.getOLGeometry().getCentroid().x;
urPos.lat = ureq.getOLGeometry().getCentroid().y;
}
else
{
if(ureq.attributes.geometry.realX === undefined)
{
urPos.lon = ureq.attributes.geometry.x;
urPos.lat = ureq.attributes.geometry.y;
}
else
{
urPos.lon = ureq.attributes.geometry.realX;
urPos.lat = ureq.attributes.geometry.realY;
}
}
urPos = uroUtils.ConvertMercatorToWGS84(urPos);
let urLink = document.location.href;
let urLayers = '';
urLink = urLink.substring(0, urLink.indexOf('?zoomLevel'));
urLink += '?zoomLevel=17&lat='+urPos.lat+'&lon='+urPos.lon+urLayers;
if(uroPopup.isUR) urLink += '&mapUpdateRequest='+uroMarkers.id;
else if(uroPopup.isTurnProb) urLink += '&showturn='+uroMarkers.id+'&endshow';
else if(uroPopup.isProblem) urLink += '&mapProblem='+uroMarkers.id;
else if(uroPopup.isPlaceUpdate) urLink += '&venueUpdateRequest='+uroMarkers.id;
let targetTab = "_uroTab_" + Math.round(Math.random()*1000000);
uroPopup.result += '<hr>Open in: <a href="'+urLink+'" id="_openInNewTab" target="'+targetTab+'">new tab</a>';
uroPopup.hasOpenInNewTabLink = true;
// add "open new livemap tab" link
let lmLink = null;
if(document.getElementById("livemap-link") != null)
{
uroDBG.AddLog('Livemap link in livemap-link id element');
lmLink = document.getElementById("livemap-link").href;
}
else if(document.getElementsByClassName("livemap-link") != null)
{
uroDBG.AddLog('Livemap link in livemap-link class element');
lmLink = document.getElementsByClassName("livemap-link")[0].href;
}
else
{
uroDBG.AddLog('Livemap link not found...');
}
if(lmLink !== null)
{
let zpos = lmLink.indexOf('?');
if(zpos > -1) lmLink = lmLink.substring(0, zpos);
lmLink += '?zoom=17&lat='+urPos.lat+'&lon='+urPos.lon+'&layers=BTTTT';
uroPopup.result += ' | <a href="'+lmLink+'" target="_lmTab">new livemap tab</a>';
}
if(!uroPopup.isPlaceUpdate)
{
// add "ignore for this session" link
uroPopup.result += '<br><a href="#" id="_addtoignore">Hide for this session</a>';
uroPopup.hasIgnoreLink = true;
}
uroPopup.Show();
},
Preamble: function()
{
let retval = true;
if
(
(uroMarkers.type === null) &&
(uroPopup.timer === 0)
)
{
if(uroPopup.shown === true)
{
uroPopup.Hide();
}
uroMousedOverMapComment = null;
retval = false;
}
if(retval === true)
{
if(uroMTEMode)
{
retval = false;
}
}
if(retval === true)
{
if(!uroInit.initialised)
{
retval = false;
}
}
if(retval === true)
{
if((uroMouseIsDown) && (uroMarkers.mouseButtons === 0))
{
uroDBG.AddLog('trapped erroneous mousedown state');
uroMouseIsDown = false;
}
if(uroMouseIsDown)
{
retval = false;
}
}
if(retval === true)
{
if(OpenLayers === null)
{
if(uroNullOpenLayers === false)
{
uroDBG.AddLog('caught null OpenLayers');
uroNullOpenLayers = true;
}
retval = false;
}
else
{
uroNullOpenLayers = false;
}
}
if(retval === true)
{
if(uroLayers.LayerVisible(uroLayers.layers[uroLayersFT.ID.UR].name) === null)
{
if(uroNullURLayer === false)
{
uroDBG.AddLog('caught null UR layer');
uroNullURLayer = true;
}
retval = false;
}
else
{
uroNullURLayer = false;
}
}
if(retval === true)
{
if(uroLayers.LayerVisible(uroLayers.layers[uroLayersFT.ID.MP].name) === null)
{
if(uroNullProblemLayer === false)
{
uroDBG.AddLog('caught null problem layer');
uroNullProblemLayer = true;
}
retval = false;
}
else
{
uroNullProblemLayer = false;
}
}
if(retval === true)
{
if(uroUtils.GetCBChecked('_cbMasterEnable') === false)
{
retval = false;
}
}
if(retval === true)
{
if(uroTestPointerOutsideMap(uroMarkers.clientX, uroMarkers.clientY))
{
retval = false;
}
}
if(retval === true)
{
uroPopup.mouseX = uroMarkers.mouseX;
uroPopup.mouseY = uroMarkers.mouseY;
uroPopup.result = '';
uroPopup.hasIgnoreLink = false;
uroPopup.hasDeleteLink = false;
uroPopup.hasAddWatchLink = false;
uroPopup.hasRemoveWatchLink = false;
uroPopup.hasUpdateWatchLink = false;
uroPopup.hasOpenInNewTabLink = false;
uroPopup.isVenue = false;
uroPopup.isMapComment = false;
uroPopup.newPopupType = null;
uroPopup.ureqID = null;
uroPopup.isUR = false;
uroPopup.isProblem = false;
uroPopup.isTurnProb = false;
uroPopup.isPlaceUpdate = false;
uroPopup.renderIntent = uroGetFeatureRenderIntent(uroMarkers.obj); //// TBC
uroInit.WazeBits();
let mouseLonLat = sdk.Map.getLonLatFromPixel({x: uroPopup.mouseX, y: uroPopup.mouseY});
let mousePoint = new OpenLayers.Geometry.Point(mouseLonLat.lon, mouseLonLat.lat);
if(uroMousedOverMapComment !== null)
{
let mcObj = uroMapComments.GetMCFeatureByID(uroMousedOverMapComment);
if(mcObj === null)
{
uroDBG.AddLog('clearing uroMousedOverMapComment: object no longer exists in current map view');
uroMousedOverMapComment = null;
}
else if(mcObj.renderIntent === "default")
{
uroDBG.AddLog('clearing uroMousedOverMapComment: pointer no longer within comment boundary');
uroMousedOverMapComment = null;
}
}
let popupXOffset = document.getElementById('editor-container').getBoundingClientRect().x;
let popupYOffset = $(document.getElementById("WazeMap")).offset().top;
uroPopup.pX = uroPopup.mouseX + popupXOffset + 10;
uroPopup.pY = uroPopup.mouseY + popupYOffset;
}
return retval;
},
Show: function()
{
if(uroPopup.suppressed === false)
{
if((uroFID != uroShownFID) || (uroPopup.newPopupType != uroPopup.shownType))
{
if(uroFID != uroShownFID) uroDBG.AddLog('FID mismatch, show popup: '+uroFID+'/'+uroShownFID);
else uroDBG.AddLog('Popup type mismatch: '+uroPopup.newPopupType+'/'+uroPopup.shownType);
uroShownFID = uroFID;
uroPopup.shownType = uroPopup.newPopupType;
uroPopup.shown = false;
}
if(uroPopup.shown === false)
{
uroPopup.shown = true;
uroDiv.style.height = "auto";
uroDiv.style.width = "auto";
uroDiv.style.overflow = "auto";
uroDiv.innerHTML = uroUtils.ModifyHTML(uroPopup.result);
if((uroFID != -1) && (uroPopup.hasIgnoreLink === true))
{
uroUtils.AddEventListener('_addtoignore','click', uroIgnore.Add, true);
}
if(uroPopup.hasOpenInNewTabLink === true)
{
uroUtils.AddEventListener('_openInNewTab','mouseup', uroOpenNewTab, true);
}
// restrict the popup width to be no wider than just under half the window width to avoid it
// completely overlapping the marker it's associated with - by keeping it to just below half
// the window width we guarantee that it'll fit either to the left or the right of the marker
// no matter how far across the screen the marker is located...
let rw = parseInt(uroDiv.clientWidth);
if(rw > (window.innerWidth * 0.45))
{
rw = (window.innerWidth * 0.45);
uroDiv.style.width = rw+'px';
}
// get the div height after any adjustment of the width above, to account for whatever content
// reflow may have occurred as a result of reducing the width...
let rh = parseInt(uroDiv.clientHeight);
// similarly restrict the popup height to avoid it dropping of the bottom of the screen if a
// segment has a bunch of closures/restrictions
rh = parseInt(uroDiv.clientHeight);
if(rh > (window.innerHeight * 0.80))
{
rh = (window.innerHeight * 0.80);
uroDiv.style.height = rh+'px';
uroDiv.style.overflow = 'scroll';
}
let origPopupX = uroPopup.pX;
let movedLeft = false;
if((uroPopup.pX + rw) > window.innerWidth)
{
// where the popup would be off the right hand side of the screen, move it completely over to the
// other side of the mouse pointer
uroPopup.pX -= (rw + 20);
if(uroPopup.pX < 0) uroPopup.pX = 0;
movedLeft = true;
}
if((uroPopup.pY + rh) > window.innerHeight)
{
// where the popup would be off the bottom of the screen, shift it up just far enough to be
// fully visible
uroPopup.pY -= (((uroPopup.pY + rh) - window.innerHeight) + 30);
}
if(uroPopup.pY < 0) uroPopup.pY = 0;
uroDiv.style.top = uroPopup.pY+'px';
uroDiv.style.left = uroPopup.pX+'px';
if(movedLeft === true)
{
// after relocating the popup to the left of the pointer, it may end up resizing itself
// which may then cause it to completely overlap the UR marker, so perform one more check
// of the div width and nudge to the left if required...
rw = parseInt(uroDiv.clientWidth);
if(rw > (window.innerWidth * 0.45))
{
rw = (window.innerWidth * 0.45);
uroDiv.style.width = rw+'px';
}
let nudgeDist = parseInt(20 + (uroPopup.pX + rw) - origPopupX);
if((uroPopup.pX + rw + 30) >= origPopupX)
{
uroDiv.style.left = parseInt(uroPopup.pX - nudgeDist)+'px';
}
}
uroDBG.AddLog('display popup at '+uroPopup.pX+','+uroPopup.pY);
uroDiv.style.visibility = 'visible';
uroPopup.autoHideTimer = (uroUtils.GetElmValue('_inputPopupAutoHideTimeout') * 10);
}
uroPopup.timer = -1;
}
},
Generate: function()
{
if(uroPopup.Preamble() === true)
{
if(uroMarkers.type === uroLayersFT.ID.UR) uroPopup.UR();
else if(uroMarkers.type === uroLayersFT.ID.MP) uroPopup.MP();
else if(uroMarkers.type === uroLayersFT.ID.VPUR) uroPopup.PUR();
else if(uroMarkers.type === "venue") uroPopup.Venue();
else if(uroMarkers.type === "comment") uroPopup.Comment();
else if(uroMarkers.type === "segment") uroPopup.Segment();
else if(uroMarkers.type === "node") uroPopup.Node();
else
{
uroDBG.AddLog("request to generate popup for unhandled type - " + uroMarkers.type);
}
}
},
Hide: function()
{
if(uroPopup.shown)
{
uroDiv.style.visibility = 'hidden';
uroPopup.shown = false;
uroPopup.timer = -2;
uroShownFID = -1;
}
uroPopup.suppressed = false;
},
Suppress: function()
{
uroDiv.style.visibility = 'hidden';
window.getSelection().removeAllRanges();
uroPopup.suppressed = true;
},
MouseOver: function()
{
uroPopup.mouseIn = true;
},
MouseOut: function()
{
uroPopup.mouseIn = false;
}
};
const uroURExtras = // UR marker enhancements
{
urList : [],
URListEntry: function(id, customType, hasMyComments, nComments, ageLastComment)
{
this.id = id;
this.customType = customType;
this.hasMyComments = hasMyComments;
this.nComments = nComments;
this.ageLastComment = ageLastComment;
},
AddToList: function(urID, customType, hasMyComments, nComments, ageLastComment)
{
uroURExtras.urList.push(new uroURExtras.URListEntry(urID, customType, hasMyComments, nComments, ageLastComment));
},
RemoveCommentCounts: function()
{
let toRemove = document.querySelectorAll('#uroCommentCount');
let trCount = toRemove.length;
while(trCount > 0)
{
--trCount;
toRemove[trCount].remove();
}
},
AddCommentCounts: function()
{
// Remove existing count elements before (re-)rendering, as WME doesn't automatically do this
// for us now that the way it handles the marker layers has changed...
uroURExtras.RemoveCommentCounts();
let addCommentCount = false;
let nURs = uroURExtras.urList.length;
if(uroUtils.GetCBChecked('_cbMasterEnable') === true)
{
addCommentCount = ((nURs > 0) && (uroUtils.GetCBChecked('_cbCommentCount') === true));
}
if(addCommentCount === true)
{
for(let i = 0; i < nURs; ++i)
{
let uObj = uroURExtras.urList[i];
let nComments = uObj.nComments;
let marker = uroGetMarker(uroLayersFT.ID.UR, uObj.id);
if((marker !== null) && (nComments !== 0))
{
let mx = (marker.x.baseVal.value - 15);
let my = (marker.y.baseVal.value + 35);
let newSpan = '<foreignObject id="uroCommentCount" x="' + mx + '" y="' + my + '" width="100%" height="100%" style="pointer-events: none;">';
newSpan += '<div style="position:absolute;';
if(uObj.hasMyComments === true)
{
newSpan += 'background-color: yellow;';
}
else
{
newSpan += 'background-color: white;';
}
newSpan += 'font-size: 14px;font-weight: 800;';
newSpan += 'border-width: 1px;border-style: solid;border-radius: 12px;padding-left: 4px;padding-right: 4px;z-index: 1;';
newSpan += '">';
newSpan += nComments + 'c ';
newSpan += uObj.ageLastComment + 'd';
newSpan += '</div>';
newSpan += '</foreignObject>';
marker.insertAdjacentHTML("afterend", newSpan);
}
}
}
}
};
const uroInit = // script initialisation
{
initialised : false,
setupListeners : true,
finalisingListenerSetup : false,
GetClassName: function(rText, nameFragment)
{
let retval = null;
let fragPos = rText.indexOf('.' + nameFragment);
if(fragPos !== -1)
{
endPos = rText.indexOf('{', fragPos);
if(endPos !== -1)
{
retval = rText.substring(fragPos + 1, endPos);
}
}
return retval;
},
GetRTCStyles: async function()
{
// CORS prevents us simply reading the CSS info directly from the document, so we bypass this
// by finding any of the CSS-related URLs in <head> which suggest they'll contain the styles
// relating to RTC markers, then fetching them directly to extract the style names...
let headLinks = document.head.getElementsByTagName("link");
for(let i = 0; i < headLinks.length; ++i)
{
let href = headLinks[i].href
if(href.indexOf("css/app") !== -1)
{
let response = await window.fetch(href);
if(response.status === 200)
{
let rText = await response.text();
if(rText.indexOf('.'+rtcClasses.marker.prefix) !== -1)
{
rtcClasses.marker.prefix = (uroInit.GetClassName(rText, rtcClasses.marker.prefix));
rtcClasses.marker.active = (uroInit.GetClassName(rText, rtcClasses.marker.active));
rtcClasses.marker.finished = (uroInit.GetClassName(rText, rtcClasses.marker.finished));
rtcClasses.marker.notStarted = (uroInit.GetClassName(rText, rtcClasses.marker.notStarted));
rtcClasses.arrow.generic = (uroInit.GetClassName(rText, rtcClasses.arrow.generic));
rtcClasses.arrow.active = (uroInit.GetClassName(rText, rtcClasses.arrow.active));
rtcClasses.arrow.finished = (uroInit.GetClassName(rText, rtcClasses.arrow.finished));
rtcClasses.arrow.notStarted = (uroInit.GetClassName(rText, rtcClasses.arrow.notStarted));
break;
}
}
}
}
// To avoid creating multiple URO tabs in the event that the initialise code
// gets called more than once, we now test for the presence of our tab and skip
// if it's already present
if(document.getElementById('uroTabHeader') === null)
{
uroInit.SetupUI();
}
},
WazeBitsAvailable: function()
{
uroDBG.AddLog('All WazeBits present and correct...');
W.prefs.on('change:isImperial', uroInit.Initialise);
uroInit.GetRTCStyles();
uroLayers.Init();
},
AddInterceptor: function()
{
uroDBG.AddLog('Adding interceptor functions...');
// add interceptor function for window.confirm(), to inhibit the closure deletion confirmation that would
// pop up for each individual closure when we're using the delete all button - the user has already
// confirmed the delete action using our own requester
const _confirm = window.confirm;
window.confirm = function(msg)
{
let cm_delete_confirm = I18n.lookup("closures.delete_confirm").split('"')[0].trimRight(1);
if(msg.indexOf(cm_delete_confirm) != -1)
{
uroDBG.AddLog('intercepted closure delete confirmation...');
if(uroRTCClone.ConfirmDelete)
{
return _confirm(msg);
}
else
{
return true;
}
}
else if(typeof(msg) == 'undefined')
{
uroDBG.AddLog('Intercepted blank confirmation...');
return true;
}
else
{
return _confirm(msg);
}
};
// add interceptor function for window.fetch(), so we can workaround the present lack of SDK support for
// finding the rank of other users, by building up our own local cache of user details as they come in
// from the server each time WME itself asks for them...
const _fetch = window.fetch;
window.fetch = async (...args) =>
{
let [resource, config ] = args;
const response = await _fetch(resource, config);
// Intercept any response which might include user details
if(response.url !== undefined)
{
if(response.url.indexOf('Features') != -1)
{
response
.clone()
.json()
.then(body => uroInit.ParseFeaturesResponse(body));
}
}
// This is a transparent interception, so all responses are passed back to WME without modification
return response;
};
uroConfirmIntercepted = true;
},
ParseFeaturesResponse: function(body)
{
// Add any user details to our cache if they're not already present...
if(body?.users?.objects !== undefined)
{
for(let i = 0; i < body.users.objects.length; ++i)
{
uroUtils.AddUserToCache(body.users.objects[i]);
}
uroUtils.CleanUserCache();
}
},
Initialise: function()
{
uroInit.initialised = false;
uroInit.setupListeners = true;
uroInit.finalisingListenerSetup = false;
if(typeof getWmeSdk !== 'function')
{
window.setTimeout(uroInit.Initialise, 100);
}
else
{
sdk = getWmeSdk({scriptId:"uro+",scriptName:"URO+"});
sdk.Events.once({eventName: 'wme-ready'}).then(uroInit.WazeBitsAvailable);
}
},
WaitForControlsContainer: function()
{
if(document.getElementById('uroControlsContainer') === null)
{
window.setTimeout(uroInit.WaitForControlsContainer,500);
}
else
{
let updateURL = 'https://greasyfork.org/scripts/1952-uroverview-plus-uro';
uroDBG.AddLog('adding controls to sidebar container...');
let tabbyHTML = '<div id="uroTabHeader"><b><a href="'+updateURL+'" target="_blank">UROverview Plus</a></b> <label id="_uroVersion">'+uroRelease.version+'</label>';
tabbyHTML += '<label id="_uroDebugMode">(dbg)</label>';
tabbyHTML += ' <input type="checkbox" id="_cbMasterEnable" checked>Enabled</input>';
tabbyHTML += uroTabs.CreateTabHeaders();
tabbyHTML += '</div>';
document.getElementById('uroControlsContainer').innerHTML = uroUtils.ModifyHTML(tabbyHTML);
uroTabs.CreateTabBodies();
// other sidebar elements
uroAMList = document.createElement('div');
uroAMList.style.color = "#ffff00";
uroCtrlHides = document.createElement('div');
// footer for tabs container
uroCtrlHides.id = 'uroCtrlHides';
let tHTML = '<input type="button" id="_btnUndoLastHide" value="Undo last hide" /> ';
tHTML += '<input type="button" id="_btnClearSessionHides" value="Undo all hides" /><p>';
uroCtrlHides.innerHTML = uroUtils.ModifyHTML(tHTML);
// footer for AM list
uroAMList.id = 'uroAMList';
uroTabs.CtrlTabs[0][uroTabs.FIELDS.SHOWFN]();
window.addEventListener("beforeunload", uroConfig.SaveSettings, false);
}
},
AddTab_API: async function()
{
let {tabLabel, tabPane} = W.userscripts.registerSidebarTab("URO+");
tabLabel.innerText = "URO+";
tabPane.innerHTML = uroUtils.ModifyHTML(uroControls.innerHTML);
await W.userscripts.waitForElementConnected(tabPane);
uroInit.CompleteUISetup();
},
CompleteUISetup: function()
{
uroDBG.AddLog('waiting for controls container...');
uroInit.WaitForControlsContainer();
uroGetProblemTypes();
uroTabs.AddToDOM();
document.getElementById('uroControlsContainer').appendChild(uroCtrlHides);
uroInit.WazeBits();
uroDiv.addEventListener("mouseover", uroPopup.MouseOver, false);
uroDiv.addEventListener("mouseout", uroPopup.MouseOut, false);
if(sessionStorage.UROverview_FID_IgnoreList === undefined) sessionStorage.UROverview_FID_IgnoreList = '';
if(sessionStorage.UROverview_FID_WatchList === undefined) sessionStorage.UROverview_FID_WatchList = '';
if(uroConfirmIntercepted === false) uroInit.AddInterceptor();
uroUtils.AddStyle('urostyle_UnstackedMarkers', '.map-marker.marker-selected { transform: scale(1) !important; }');
uroMainTickHandlerID = window.setInterval(uroMainTick,1000);
},
SetupUI: function()
{
// create a new div to display the UR details floaty-box
uroDiv = document.createElement('div');
uroDiv.id = "uroDiv";
uroDiv.style.position = 'absolute';
uroDiv.style.visibility = 'hidden';
uroDiv.style.top = '0';
uroDiv.style.left = '0';
uroDiv.style.zIndex = 10000;
uroDiv.style.backgroundColor = 'aliceblue';
uroDiv.style.borderWidth = '3px';
uroDiv.style.borderStyle = 'solid';
uroDiv.style.borderRadius = '10px';
uroDiv.style.boxShadow = '5px 5px 10px Silver';
uroDiv.style.padding = '4px';
document.body.appendChild(uroDiv);
// create a new div to display script alerts
uroAlerts = document.createElement('div');
uroAlerts.id = "uroAlerts";
uroAlerts.style.position = 'fixed';
uroAlerts.style.visibility = 'hidden';
uroAlerts.style.top = '50%';
uroAlerts.style.left = '50%';
uroAlerts.style.zIndex = 10000;
uroAlerts.style.backgroundColor = 'aliceblue';
uroAlerts.style.borderWidth = '3px';
uroAlerts.style.borderStyle = 'solid';
uroAlerts.style.borderRadius = '10px';
uroAlerts.style.boxShadow = '5px 5px 10px Silver';
uroAlerts.style.padding = '4px';
uroAlerts.style.transform = "translate(-50%, -50%)";
let alertsHTML = '<div id="header" style="padding: 4px; background-color:LightGreen; font-weight: bold;">Alert title goes here...</div>';
alertsHTML += '<div id="content" style="padding: 4px; background-color:White; overflow:auto;max-height:500px">Alert content goes here...</div>';
alertsHTML += '<div id="controls" align="center" style="padding: 4px;">';
alertsHTML += '<span id="uroAlertTickBtn" style="cursor:pointer;font-size:14px;border:thin outset black;padding:2px 10px 2px 10px;">';
alertsHTML += '<i class="fa fa-check"> </i>';
alertsHTML += '<span id="uroAlertTickBtnCaption" style="font-weight: bold;"></span>';
alertsHTML += '</span>';
alertsHTML += ' ';
alertsHTML += '<span id="uroAlertCrossBtn" style="cursor:pointer;font-size:14px;border:thin outset black;padding:2px 10px 2px 10px;">';
alertsHTML += '<i class="fa fa-times"> </i>';
alertsHTML += '<span id="uroAlertCrossBtnCaption" style="font-weight: bold;"></span>';
alertsHTML += '</span>';
alertsHTML += '</div>';
uroAlerts.innerHTML = uroUtils.ModifyHTML(alertsHTML);
document.body.appendChild(uroAlerts);
uroControls = document.createElement('section');
uroControls.style.fontSize = '12px';
uroControls.style.height = '100%';
uroControls.id = "sidepanel-uroverview";
uroControls.className = "tab-pane";
uroControls.innerHTML = uroUtils.ModifyHTML('<div id="uroControlsContainer" style="display:flex;flex-direction:column;height:80vh;"></div>');
uroInit.AddTab_API();
},
WazeBits: function()
{
uroMCLayer = null;
let layers = W.map.getLayers();
for(let i=0; i < layers.length; i++)
{
if(layers[i].name == 'mapComments') uroMCLayer = layers[i];
if(layers[i].name == 'venues') uroVenueLayer = layers[i];
}
},
FinalizeListenerSetup: function()
{
uroInit.finalisingListenerSetup = true;
// filter markers when the marker objects are modified (this happens whenever WME needs to load fresh marker data
// due to having panned/zoomed the map beyond the extents of the previously loaded data)
W.model.mapUpdateRequests.on("objectschanged", uroFilterURs_onObjectsChanged);
W.model.mapUpdateRequests.on("objectsadded", uroFilterURs_onObjectsAdded);
W.model.mapUpdateRequests.on("objectsremoved", uroFilterURs_onObjectsRemoved);
W.model.updateRequestSessions.on("objectsadded", uroUREvent_onObjectsAdded);
W.model.mapProblems.on("objectschanged", uroFilterProblems);
W.model.mapProblems.on("objectsadded", uroFilterProblems);
W.model.mapProblems.on("objectsremoved", uroFilterProblems);
W.model.venues.on("objectschanged", uroFilterPlaces);
W.model.venues.on("objectsadded", uroFilterPlaces);
W.model.venues.on("objectsremoved", uroFilterPlaces);
W.model.mapComments.on("objectschanged", uroLayers.MCLayerChanged_changed);
W.model.mapComments.on("objectsadded", uroLayers.MCLayerChanged_added);
W.model.mapComments.on("objectsremoved", uroLayers.MCLayerChanged_removed);
uroMarkers.RegisterEvents();
uroLayers.InitialiseMOs();
uroUtils.AddEventListener('_btnUndoLastHide', "click", uroIgnore.RemoveLastAdded, true);
uroUtils.AddEventListener('_btnClearSessionHides', "click", uroIgnore.RemoveAll, true);
uroIgnore.EnableControls();
uroUtils.AddEventListener('_btnSettingsToText', "click", uroConfig.SettingsToText, true);
uroUtils.AddEventListener('_btnTextToSettings', "click", uroConfig.TextToSettings, true);
uroUtils.AddEventListener('_btnResetSettings', "click", uroConfig.DefaultSettings, true);
uroUtils.AddEventListener('_btnClearSettingsText', "click", uroConfig.ClearSettingsText, true);
uroUtils.AddEventListener('_cbMasterEnable', "click", uroFilterItems_MasterEnableClick, true);
/*
uroUtils.AddEventListener('_btnDebugToScreen',"click", uroDBG.Dump, true);
*/
uroUtils.AddEventListener('uroDiv', "dblclick", uroPopup.Suppress, true);
uroUtils.AddEventListener('_selectPlacesUserID', "change", uroPlacesEditorSelected, true);
uroUtils.AddEventListener('_selectHidePlacesUserID', "change", uroHidePlacesEditorSelected, true);
uroUtils.AddEventListener('uroAlertTickBtn', 'click', uroAlertBox.CloseWithTick, true);
uroUtils.AddEventListener('uroAlertCrossBtn', 'click', uroAlertBox.CloseWithCross, true);
for(let i = 0; i < uroTabs.CtrlTabs.length; ++i)
{
uroUtils.SetOnClick(uroTabs.CtrlTabs[i][uroTabs.FIELDS.LINKID], uroTabs.CtrlTabs[i][uroTabs.FIELDS.SHOWFN]);
}
for(let idx = 0; idx < sdk.DataModel.Venues.getVenueMainCategories().length; idx++)
{
uroUtils.SetOnClick('_uroPlacesGroupState-'+idx,uroPlacesGroupCollapseExpand);
}
uroDBG.AddLog('finalise onload');
uroNewLookCheckDetailsRequest();
// filter markers as and when the map is moved
W.map.events.register("moveend", null, uroMapMoveEnd.Handler);
W.map.events.register("moveend", null, uroGetAMs); // uroGetAMs accesses e, so has to be called directly from the event handler
W.map.events.register("mousemove", null, uroGetAMs);
W.map.events.register("mousemove", null, uroMarkers.MouseMove);
W.map.events.registerPriority("mousedown", null, uroMouseDown);
// trap mousedown on Streetview marker drag
if(document.getElementsByClassName('street-view-control').length === 0) return;
document.getElementsByClassName('street-view-control')[0].onmousedown = uroMouseDown;
W.map.events.register("mouseup", null, uroMouseUp);
W.map.events.register("mouseout", null, uroMouseOut);
uroSetSectionTabStyles();
uroConfig.LoadSettings();
uroUITweaks.ChangeClustering();
uroDBG.AddLog('getting user name...');
uroUserName = sdk.State.getUserInfo();
if(uroUserName !== null)
{
uroUserName = uroUserName.userName;
uroDBG.AddLog('...name is '+uroUserName);
}
else
{
uroDBG.AddLog('...user is not logged in yet :-/');
}
uroDBG.AddLog('filtering...');
uroFilterItems();
uroDBG.AddLog('...done');
uroDBG.showDebugOutput = uroDBG.persistentDebugOutput;
let dbgMode = "none";
if(uroDBG.showDebugOutput)
{
dbgMode = "inline";
}
document.getElementById('_uroDebugMode').style.display = dbgMode;
uroUtils.AddEventListener('_uroVersion',"click", uroDBG.Toggle, true);
// add exclusiveCB click handlers to all checkboxes with a pairedWith attribute
uroDBG.AddLog('adding exclusiveCB handlers...');
let cbList = document.getElementsByTagName('input');
for (let optIdx=0;optIdx<cbList.length;optIdx++)
{
if((cbList[optIdx].id.indexOf('_cb') === 0) && (cbList[optIdx].attributes.pairedWith != null))
{
uroUtils.SetOnClick(cbList[optIdx].id,uroExclusiveCB);
}
}
uroDBG.AddLog('...done');
// manually call the layer-change handlers on startup, since there's a good chance WME will already have
// completed its own startup layer changes before our handlers get registered, preventing the marker handlers
// from being set up as expected on any markers which are visible in the startup map view before the user forces
// a layer update by panning/zooming/etc...
uroLayers.RunChangeHandlers();
uroUITweaks.Setup();
uroInit.setupListeners = false;
uroMainTickStage = 0;
window.clearInterval(uroMainTickHandlerID);
window.setInterval(uroMainTick, 10);
uroInit.initialised = true;
}
};
const uroUITweaks = // native UI enhancements
{
MO_SidePanel : null,
MO_ReportPanel : null,
ChangeMapBGColour: function()
{
let mapviewport = document.getElementById("WazeMap").getElementsByClassName("olMapViewport")[0];
if((uroUtils.GetCBChecked('_cbWhiteBackground') === true) && (uroUtils.GetCBChecked('_cbMasterEnable') === true))
{
let customColour = '#' + uroUtils.ToHex(uroUtils.GetElmValue('_inputCustomBackgroundRed'),2);
customColour += uroUtils.ToHex(uroUtils.GetElmValue('_inputCustomBackgroundGreen'),2);
customColour += uroUtils.ToHex(uroUtils.GetElmValue('_inputCustomBackgroundBlue'),2);
mapviewport.style.setProperty('background',customColour,'important');
}
else
{
mapviewport.style.setProperty('background',"#354148",'important');
}
},
HideAMLayer: function()
{
// If this sounds like a weird function - why not just switch off the layer from the layers menu? - then
// remember that in order for URO+ to be able to display in its own tab the list of AMs under the current
// mouse pointer location, which is somewhat more useful than the list given in the topbar, it needs the
// AM layer to be activated so that the AM areas data is loaded into WME. It doesn't however need the layer
// to then be visible, and since having a bunch of purple polygons covering the map can make for a rather
// difficult editing experience, being able to hide the polys whilst retaining the area information is
// of real benefit...
if((uroUtils.GetCBChecked('_cbHideAMLayer')) && (uroUtils.GetCBChecked('_cbMasterEnable')))
{
W.map.managedAreasLayer.setOpacity(0);
}
else
{
W.map.managedAreasLayer.setOpacity(1);
}
},
HideSegments: function()
{
// Hides the vector segments when the raster segment layer is hidden
if(uroUtils.GetCBChecked('_cbHideSegmentsWhenRoadsHidden'))
{
W.map.segmentLayer.drawn = W.map.roadLayer.visibility;
W.map.nodeLayer.drawn = W.map.roadLayer.visibility;
}
else
{
W.map.segmentLayer.drawn = true;
W.map.nodeLayer.drawn = true;
}
},
SetClusteringFor: function(layerID, toDisable)
{
let strat = uroLayers.layers[layerID].l.strategies;
if((strat !== undefined) && (strat.length > 0))
{
if(toDisable === true)
{
strat[0].threshold = 100000;
}
else
{
strat[0].threshold = 10;
}
}
},
ChangeClustering: function()
{
uroUITweaks.SetClusteringFor(uroLayersFT.ID.UR, uroUtils.GetCBChecked('_cbInhibitURClusters'));
uroUITweaks.SetClusteringFor(uroLayersFT.ID.MP, uroUtils.GetCBChecked('_cbInhibitMPClusters'));
uroUITweaks.SetClusteringFor(uroLayersFT.ID.PUR, uroUtils.GetCBChecked('_cbInhibitPUClusters'));
uroUITweaks.SetClusteringFor(uroLayersFT.ID.PPUR, uroUtils.GetCBChecked('_cbInhibitPUClusters'));
uroUITweaks.SetClusteringFor(uroLayersFT.ID.RPUR, uroUtils.GetCBChecked('_cbInhibitPUClusters'));
uroUITweaks.SetClusteringFor(uroLayersFT.ID.SegSug, uroUtils.GetCBChecked('_cbInhibitESClusters'));
uroUITweaks.SetClusteringFor(uroLayersFT.ID.EditSug, uroUtils.GetCBChecked('_cbInhibitESClusters'));
// If the map is zoomed out far enough such that clustering could be occurring, perform a
// zoom in-out to force a redraw of the markers based on whatever the new clustering
// settings are...
if(sdk.Map.getZoomLevel() < 16)
{
sdk.Map.zoomIn();
sdk.Map.zoomOut();
}
},
ReportPanelChange: function()
{
// Inhibit map re-centering when opening a report
if(uroMarkers.inhibitSetCenter === true)
{
let bcr = document.querySelector('#panel-container').getBoundingClientRect();
if(bcr.width > 0)
{
uroMarkers.inhibitSetCenter = false;
sdk.Map.setMapCenter()(uroMarkers.clickedOnCenter);
}
}
// "panel-container" now also gets used to show the turn closure UI, so reuse this MO handler
// as a way to also apply the MTE dropdown fix here...
let mteDropDown = document.querySelector('#panel-container #closure_eventId');
if(mteDropDown !== undefined)
{
uroRTCs.FixMTEDropDown(mteDropDown);
}
},
CheckForClosurePanel: function()
{
let cl = document.querySelector('.closures-list');
let ct = document.querySelector('wz-tab.closures-tab');
if((cl !== null) && (ct !== null))
{
// Closure panel active
let uroMO_ClosureUI = new MutationObserver(uroRTCs.ClosureEditUIChanged);
uroMO_ClosureUI.disconnect();
uroMO_ClosureUI.observe(ct ,{subtree: true, attributes: true});
uroRTCs.ClosureEditUIChanged();
let uroRO_ClosureUI = new ResizeObserver(uroRTCs.ScrollToEndOfClosures);
uroRO_ClosureUI.disconnect();
uroRO_ClosureUI.observe(cl);
uroRTCs.ScrollToEndOfClosures();
}
},
SidePanelChange: function()
{
// The sidepanel MO only fires when the sidepanel changes in bulk - i.e. when first rendered, or
// when changing to show details for a different type of map object. Once this version of the
// panel is open, any internal changes, such as selecting a different tab within the panel, do
// NOT trigger the MO. Consequently, if a segment is selected and the sidepanel doesn't then
// open with the closures tab already selected, the MO will fire at this point rather than at the
// point where the closures tab is selected...
//
// To ensure the closures UI enhancements are applied consistently, we therefore need to set up an
// onclick handler to deal with the sidepanel opening into a different tab and the user then
// clicking through into the closures tab after this MO has already fired, but we ALSO still need
// to check for the availability of the closures UI here as well just in case the sidepanel opens
// into the closures tab directly.
//
// Easy really...
let elmToClick = document.querySelector('#edit-panel');
if(elmToClick !== null)
{
uroUtils.SetOnClick(elmToClick, uroUITweaks.CheckForClosurePanel);
}
uroUITweaks.CheckForClosurePanel();
},
Setup: function()
{
uroUITweaks.MO_SidePanel = new MutationObserver(uroUITweaks.SidePanelChange);
uroUITweaks.MO_SidePanel.observe(document.getElementById('edit-panel'), {childList: true, subtree: true});
uroUITweaks.MO_ReportPanel = new MutationObserver(uroUITweaks.ReportPanelChange);
uroUITweaks.MO_ReportPanel.observe(document.getElementById('panel-container'), {childList: true, subtree: true});
uroUITweaks.ChangeMapBGColour();
uroUITweaks.HideAMLayer();
uroUITweaks.HideSegments();
}
};
const uroMapMoveEnd = // things that happen after a move of the map view
{
lat : null,
lon: null,
zoom: null,
Handler: function()
{
let mc = sdk.Map.getMapCenter();
let z = sdk.Map.getZoomLevel();
if((mc.lat != this.lat) || (mc.lon != this.lon) || (z != this.zoom))
{
// Apply any filters which need to be updated when the map view changes,
// and which won't be applied via an event handler or mutation observer
// attached to the relevant layer etc...
uroFilterProblems();
uroFilterPlaces();
uroFilterURs();
uroFilterRAs();
uroFilterMapComments();
uroMiscUITweaksHandler();
uroLayers.MCLayerChanged();
this.lat = mc.lat;
this.lon = mc.lon;
this.zoom = z;
}
}
};
const uroURSessions = // handlers for UR session data retrieval
{
pendingIDs : [],
fetchTotal : 0,
inProgress : false,
sessionData : [],
FindSessionData : function(urID)
{
return this.sessionData.findIndex(({id}) => id === urID);
},
GetSessionData : function(urID)
{
let retval = null;
let idx = uroURSessions.FindSessionData(urID);
if(idx != -1)
{
retval = this.sessionData[idx];
}
return retval;
},
GetSessionDataLength : function()
{
return this.sessionData.length;
},
Refresh : function()
{
if(this.inProgress === false)
{
this.pendingIDs = [];
this.fetchTotal = 0;
// Start by clearing the keep flags for any sessionData entries already cached, so we know which ones
// to discard...
for (let i in this.sessionData)
{
this.sessionData[i].keep = false;
}
// Now, for each UR currently loaded, see if we already have a corresponding sessionData entry, set the
// sessionData keep flag if we do, or add its ID to the sessionData fetch queue if we don't
for (let urID in W.model.mapUpdateRequests.objects)
{
if(W.model.mapUpdateRequests.objects.hasOwnProperty(urID))
{
urID = parseInt(urID);
let sdIdx = uroURSessions.FindSessionData(urID);
if(sdIdx == -1)
{
this.pendingIDs.push(urID);
++this.fetchTotal;
}
else
{
this.sessionData[sdIdx].keep = true;
}
}
}
// Next, delete any sessionData entries for URs no longer present
let sdIdx = this.sessionData.findIndex(({keep}) => keep === false);
while(sdIdx !== -1)
{
this.sessionData.splice(sdIdx, 1);
sdIdx = this.sessionData.findIndex(({keep}) => keep === false);
}
if(this.fetchTotal > 0)
{
// If the fetch queue isn't empty, set the lockout flag and start requesting
// details from the server...
this.inProgress = true;
uroDBG.AddLog('grabbing session data for ' + this.fetchTotal + ' URs, please wait...');
document.querySelector('#WazeMap').style.cursor = "progress";
this.NextRequest();
}
else
{
// Once the fetch queue is empty, apply filtering
uroDBG.AddLog('Completed UR details fetch');
document.querySelector('#WazeMap').style.cursor = "";
uroFilterURs();
}
}
},
NextRequest : function()
{
if(uroURSessions.pendingIDs.length > 0)
{
let nextID = uroURSessions.pendingIDs.shift();
sdk.DataModel.MapUpdateRequests.getUpdateRequestDetails({mapUpdateRequestId: nextID}).then(uroURSessions.SessionDataHandler).finally(uroURSessions.PromiseHandler).catch(uroURSessions.PromiseHandler);
}
else
{
uroURSessions.PromiseHandler();
}
},
SessionDataHandler : function(sd)
{
sd.keep = true;
uroURSessions.sessionData.push(sd);
},
PromiseHandler : function()
{
--uroURSessions.fetchTotal;
if(uroURSessions.fetchTotal > 0)
{
uroURSessions.NextRequest();
}
else
{
// Once we've finished receiving all the ones in the current queue, clear the lockout flag so
// we can go around again to check for any further URs requiring a details fetch...
uroURSessions.inProgress = false;
uroURSessions.Refresh();
}
}
};
const uroMapComments = // map comment/note stuff
{
GetMCFeatureByID : function (mcID)
{
let retval = null;
for(let i = 0; i < uroMCLayer.features.length; ++i)
{
if(uroMCLayer.features[i].attributes.wazeFeature.id === mcID)
{
retval = uroMCLayer.features[i];
break;
}
}
return retval;
}
};
// ================================================================================================
// Here be the unfactored wilderness...
// ================================================================================================
function uroGetAddress(streetID, houseNumber, formatForSegmentPopup, showAsToll)
{
let result = '';
if((houseNumber !== undefined) && (houseNumber !== null))
{
result += houseNumber + ' ';
}
if((streetID === null) || (streetID === undefined))
{
result += '<b>Segment name not set...</b>';
}
else
{
let streetName = I18n.lookup('edit.address.no_street');
let doesStreetIDExist = true;
let streetObj = sdk.DataModel.Streets.getById({streetId: streetID});
if(streetObj === null)
{
streetName = 'non-existent streetID';
doesStreetIDExist = false;
}
else
{
if((streetName !== null) && (streetObj.isEmpty === false))
{
streetName = streetObj.name;
}
}
if(formatForSegmentPopup === true)
{
if(showAsToll == true)
{
result += '<i class="fa fa-credit-card"></i> ';
}
result += '<b>'+streetName+'</b><br>';
}
else
{
result += streetName + ', ';
}
if(doesStreetIDExist === true)
{
let cityName = I18n.lookup('edit.address.no_city');
let doesCityIDExist = true;
let cityID = sdk.DataModel.Streets.getById({streetId: streetID}).cityId;
let cityObj = sdk.DataModel.Cities.getById({cityId: cityID});
if(cityObj === null)
{
cityName = 'non-existent cityID';
doesCityIDExist = false;
}
else
{
if(cityObj.name !== "")
{
cityName = cityObj.name;
}
}
result += cityName + ', ';
if(doesCityIDExist === true)
{
let stateID = cityObj.stateId;
let stateObj = sdk.DataModel.States.getById({stateId: stateID});
if(stateObj === null)
{
result += 'non-existent stateID';
}
else
{
result += stateObj.name;
}
}
}
}
result += '<br>';
return result;
}
function uroURHasMyComments(fid)
{
if(uroUserName === null)
{
return false;
}
let sdObj = uroURSessions.GetSessionData(fid);
if(sdObj !== null)
{
let nComments = sdObj.comments.length;
if(nComments === 0)
{
return false;
}
for(let cidx = 0; cidx < nComments; cidx++)
{
if(sdObj.comments[cidx].userName == uroUserName)
{
return true;
}
}
}
return false;
}
function uroUpdateMTEList()
{
let mtes = sdk.DataModel.MajorTrafficEvents.getAll();
if(mtes.length === 0) return;
let selectedIdx = null;
let idx;
let mteNames = [];
let mteIDs = [];
for(idx in mtes)
{
let name = mtes[idx].names[0]?.value;
if(mteNames.indexOf(name) == -1)
{
mteNames.push(name);
mteIDs.push(mtes[idx].id);
}
}
// check for any previously selected ID in the list, then clear it and repopulate
// using the newly gathered ID collection from above, and finally reselect the
// previously selected MTE if its still present in the new list...
let selector;
let selectedID;
let selectorEntry;
selector = document.getElementById('_selectRTCMTE');
selectedID = null;
if(selector.selectedOptions[0] != null)
{
selectedID = selector.selectedOptions[0].value;
}
while(selector.options.length > 0)
{
selector.options.remove(0);
}
selector.options.add(new Option('<select a MTE>', null));
if(mteNames.length > 0)
{
selectorEntry = '';
for(idx=0; idx<mteNames.length; idx++)
{
selectorEntry = mteNames[idx];
selector.options.add(new Option(selectorEntry, mteIDs[idx]));
if(mteIDs[idx] == selectedID)
{
selectedIdx = idx+1;
}
}
}
if(selectedIdx !== null)
{
selector.selectedIndex = selectedIdx;
}
}
function uroUpdateRAList()
{
let rdas = sdk.DataModel.RestrictedDrivingAreas.getAll();
if(rdas.length === 0) return;
let selectedIdx = null;
let idx;
let raNames = [];
for(idx in rdas)
{
let name = rdas[idx].name;
if(raNames.indexOf(name) == -1)
{
raNames.push(name);
}
}
// check for any previously selected name in the list, then clear it and repopulate
// using the newly gathered collection from above, and finally reselect the
// previously selected MTE if its still present in the new list...
let selector;
let selectedName;
let selectorEntry;
selector = document.getElementById('_selectRA');
selectedName = null;
if(selector.selectedOptions[0] != null)
{
selectedName = selector.selectedOptions[0].value;
}
while(selector.options.length > 0)
{
selector.options.remove(0);
}
selector.options.add(new Option('<select a RA>', null));
if(raNames.length > 0)
{
selectorEntry = '';
for(idx=0; idx<raNames.length; idx++)
{
selectorEntry = raNames[idx];
selector.options.add(new Option(selectorEntry, selectorEntry));
if(selectorEntry == selectedName)
{
selectedIdx = idx+1;
}
}
}
if(selectedIdx !== null)
{
selector.selectedIndex = selectedIdx;
}
}
function uroUpdateEditorList(modelObj, listElement, useCreated, useUpdated, useResolved, useCommenter)
{
if(Object.keys(modelObj).length === 0) return;
let selector = document.getElementById(listElement);
let selectedUser = null;
if(selector.selectedOptions[0] != null)
{
selectedUser = selector.selectedOptions[0].value;
}
while(selector.options.length > 0)
{
selector.options.remove(0);
}
let selectedIdx = null;
let listedUsers = [];
let idx;
for(idx in modelObj)
{
if(modelObj.hasOwnProperty(idx))
{
let obj;
if(useCommenter == true)
{
obj = modelObj[idx];
if(obj.attributes.comments.length > 0)
{
for(let cidx=0; cidx < obj.attributes.comments.length; cidx++)
{
let userID = obj.attributes.comments[cidx].userID;
if((listedUsers.indexOf(userID) == -1) && (userID != -1))
{
listedUsers.push(userID);
}
}
}
}
else
{
obj = modelObj[idx].modificationData;
let cbID = null;
let ubID = null;
let rbID = null;
if(useCreated == true) cbID = obj.createdBy;
if(useUpdated == true) ubID = obj.updatedBy;
if(useResolved == true) ubID = obj.resolvedBy;
if((cbID !== null) && (listedUsers.indexOf(cbID) == -1))
{
listedUsers.push(cbID);
}
if((ubID !== null) && (listedUsers.indexOf(ubID) == -1))
{
listedUsers.push(ubID);
}
if((rbID !== null) && (listedUsers.indexOf(rbID) == -1))
{
listedUsers.push(rbID);
}
}
}
}
selector.options.add(new Option('<select a user>', null));
if(listedUsers.length > 0)
{
let selectorEntry = '';
for(idx=0; idx<listedUsers.length; idx++)
{
/*
if(listedUsers.indexOf(users[idx].id) != -1)
{
listedUsers.splice(listedUsers.indexOf(users[idx]), 1);
}
*/
selectorEntry = listedUsers[idx];
selector.options.add(new Option(selectorEntry, selectorEntry));
if(selectorEntry == selectedUser)
{
selectedIdx = idx+1;
}
}
}
if(selectedIdx !== null)
{
selector.selectedIndex = selectedIdx;
}
}
function uroFilterRAs()
{
let pmTStart = performance.now();
let pmFunction = "uroFilterRAs";
if(uroFilterPreamble() === false) return;
let uFURs_masterEnable = uroUtils.IsFilteringEnabled(false);
let filterByArea = uroUtils.GetCBChecked('_cbShowSpecificRA');
let filterByLastEditor = uroUtils.GetCBChecked('_cbRAEditorIDFilter');
let filterByMinAge = uroUtils.GetCBChecked('_cbEnableRAAgeFilterLessThan');
let filterByMaxAge = uroUtils.GetCBChecked('_cbEnableRAAgeFilterMoreThan');
let thresholdMinAge = uroUtils.GetElmValue('_inputFilterRAAgeLessThan');
let thresholdMaxAge = uroUtils.GetElmValue('_inputFilterRAAgeMoreThan');
let selectorRA = document.getElementById('_selectRA');
if(filterByArea === false)
{
while(selectorRA.options.length > 0)
{
selectorRA.options.remove(0);
}
}
let shownRA = null;
if(filterByArea === true)
{
if(selectorRA.options.length === 0)
{
uroUpdateRAList();
}
if(selectorRA.selectedOptions[0] != null)
{
shownRA = selectorRA.selectedOptions[0].value;
}
}
let RAs = sdk.DataModel.RestrictedDrivingAreas.getAll();
let filterName = null;
if(filterByLastEditor == true)
{
uroUpdateEditorList(RAs, '_selectRAEditorID', true, true, false, false);
let selector = document.getElementById('_selectRAEditorID');
if(selector.selectedIndex > 0)
{
filterName = document.getElementById('_selectRAEditorID').selectedOptions[0].value;
}
}
for (let raIdx = 0; raIdx < RAs.length; raIdx++)
{
let raStyle = 'visible';
if(uFURs_masterEnable === true)
{
if(shownRA !== null)
{
if(RAs[raIdx].name != shownRA) raStyle = 'hidden';
}
if(filterName !== null)
{
if((RAs[raIdx].modificationData.createdBy != filterName) && (RAs[raIdx].modificationData.updatedBy != filterName))
{
raStyle = 'hidden';
}
}
let raAge = uroUtils.DateToDays(RAs[raIdx].modificationData.updatedOn);
if(filterByMinAge == true)
{
if(raAge < thresholdMinAge) raStyle = 'hidden';
}
if(filterByMaxAge == true)
{
if(raAge > thresholdMaxAge) raStyle = 'hidden';
}
}
let raID = RAs[raIdx].id;
let raFeatures = W.map.restrictedDrivingAreaLayer.features;
for (let fIdx = 0; fIdx < raFeatures.length; ++fIdx)
{
if(raFeatures[fIdx].attributes.wazeFeature.id === raID)
{
let geoID = raFeatures[fIdx].geometry.id;
if(document.getElementById(geoID) !== null)
{
document.getElementById(geoID).style.visibility = raStyle;
}
}
}
}
uroDBG.PerfMon(pmFunction, pmTStart);
}
function uroFilterPURMarker(markerType, pu, placeStyle)
{
let vObj = sdk.DataModel.Venues.getById({venueId: pu});
let mObj = uroGetMarker(markerType, pu);
mObj.style.visibility = placeStyle;
let urEntries = vObj.venueUpdateRequests;
if(urEntries !== undefined)
{
if (urEntries.length > 1)
{
// for PURs related to multiple change requests, we also need to apply the
// filtering to the text inside the balloon that indicates how many CRs
// the PUR represents - the balloon itself gets filtered as part of the
// main marker above, but the text is in a seperate element...
let pObj = mObj.parentNode.parentNode;
let tObjs = pObj.getElementsByTagName("text");
for(let i = 0; i < tObjs.length; ++i)
{
tObjs[i].style.visibility = placeStyle;
}
}
}
}
function uroFilterPlaceMarker(markerType, pu, uFP_masterEnable)
{
let vObj = sdk.DataModel.Venues.getById({venueId: pu});
let purAge = null;
let placeStyle = 'visible';
if(uFP_masterEnable === true)
{
if(uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] === true)
{
let tPt = [];
tPt.push(vObj.geometry.coordinates[0]);
tPt.push(vObj.geometry.coordinates[1]);
if(uroCheckGeometryWithinManagedAreas(tPt) === true) placeStyle = 'hidden';
}
if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.filterUneditable] === true))
{
if(vObj.attributes.permissions === 0)
{
placeStyle = 'hidden';
}
if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.isLoggedIn]))
{
if(uro_uFP[uroEnums.FP_OPTS.userRank] < vObj.attributes.lockRank)
{
placeStyle = 'hidden';
}
}
if((placeStyle == 'visible') && (vObj.attributes.adLocked))
{
placeStyle = 'hidden';
}
}
if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.filterLockRanked] === true))
{
if(vObj.attributes.lockRank !== 0)
{
placeStyle = 'hidden';
}
}
let urEntries = vObj.attributes.venueUpdateRequests;
if((placeStyle == 'visible') && (urEntries !== undefined))
{
hasBalloon = (urEntries.length > 1);
for(let i = 0; i < urEntries.length; ++i)
{
let ut = urEntries[i].attributes.updateType;
if((uro_uFP[uroEnums.FP_OPTS.filterFlagged] === true) && (ut === "flag"))
{
placeStyle = 'hidden';
}
else if((uro_uFP[uroEnums.FP_OPTS.filterNewPlace] === true) && (ut === "ADD_VENUE"))
{
placeStyle = 'hidden';
}
else if((uro_uFP[uroEnums.FP_OPTS.filterUpdatedDetails] === true) && (ut === "UPDATE_VENUE"))
{
placeStyle = 'hidden';
}
else if((uro_uFP[uroEnums.FP_OPTS.filterNewPhoto] === true) && (ut === "ADD_IMAGE"))
{
placeStyle = 'hidden';
}
}
if((placeStyle == 'visible') && (uro_uFP[uroEnums.FP_OPTS.filterOnCFs] === true))
{
let nVUR = urEntries.length;
while(nVUR > 0)
{
nVUR--;
let tCF = urEntries[nVUR].attributes.changedFields;
if(tCF !== undefined)
{
if(tCF.length > 0)
{
let tFN = tCF[0].attributes.fieldName;
if((tFN == "phone") && (uro_uFP[uroEnums.FP_OPTS.filterCFPhone] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "name") && (uro_uFP[uroEnums.FP_OPTS.filterCFName] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "entryExitPoints") && (uro_uFP[uroEnums.FP_OPTS.filterCFEntryExitPoints] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "openingHours") && (uro_uFP[uroEnums.FP_OPTS.filterCFOpeningHours] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "aliases") && (uro_uFP[uroEnums.FP_OPTS.filterCFAliases] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "services") && (uro_uFP[uroEnums.FP_OPTS.filterCFServices] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "geometry") && (uro_uFP[uroEnums.FP_OPTS.filterCFGeometry] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "houseNumber") && (uro_uFP[uroEnums.FP_OPTS.filterCFHouseNumber] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "categories") && (uro_uFP[uroEnums.FP_OPTS.filterCFCategories] === true))
{
placeStyle = 'hidden';
}
else if((tFN == "description") && (uro_uFP[uroEnums.FP_OPTS.filterCFDescription] === true))
{
placeStyle = 'hidden';
}
}
}
}
}
}
if(uro_uFP[uroEnums.FP_OPTS.invertPURFilters] === true)
{
if(placeStyle == 'hidden') placeStyle = 'visible';
else placeStyle = 'hidden';
}
if(uro_uFP[uroEnums.FP_OPTS.filterMinPURAge] || uro_uFP[uroEnums.FP_OPTS.filterMaxPURAge])
{
purAge = uroUtils.GetPURAge(vObj);
if(uro_uFP[uroEnums.FP_OPTS.filterMinPURAge] === true)
{
if(purAge < uro_uFP[uroEnums.FP_OPTS.thresholdMinPURDays]) placeStyle = 'hidden';
}
if(uro_uFP[uroEnums.FP_OPTS.filterMaxPURAge] === true)
{
if(purAge > uro_uFP[uroEnums.FP_OPTS.thresholdMaxPURDays]) placeStyle = 'hidden';
}
}
if(uroPURsToHide.indexOf(vObj.attributes.id) !== -1)
{
placeStyle = 'hidden';
}
}
uroFilterPURMarker(markerType, pu, placeStyle);
if((uro_uFP[uroEnums.FP_OPTS.leavePURGeos] === false) && (placeStyle === 'hidden'))
{
if(vObj.model != null)
{
if(vObj.attributes.geometry != null)
{
let puGeo = document.getElementById(vObj.attributes.geometry.id);
if(puGeo !== null)
{
puGeo.style.visibility = 'hidden';
}
}
}
}
}
function uroFilterPlaces()
{
let pmTStart = performance.now();
let pmFunction = "uroFilterPlaces";
if(uroFilterPreamble() === false) return;
let gs = sdk.Editing.getSelection();
if(gs !== null)
{
if(gs.objectType == "venue")
{
return;
}
}
if(uroUtils.GetCBChecked('_cbDisablePlacesFiltering') === true) return;
uroUpdateVenueEditorLists();
let filterNameID = null;
let tbUserName = uroUtils.GetElmValue('_textPlacesEditor');
let selector = document.getElementById('_selectPlacesUserID');
if(selector.selectedIndex > 0)
{
let selUserName = document.getElementById('_selectPlacesUserID').selectedOptions[0].innerHTML;
if(selUserName == tbUserName)
{
filterNameID = document.getElementById('_selectPlacesUserID').selectedOptions[0].value;
}
}
filterNameID = uroUtils.GetUserID(filterNameID, tbUserName);
let filterHideNameID = null;
let tbHideUserName = uroUtils.GetElmValue('_textHidePlacesEditor');
let selectorHide = document.getElementById('_selectHidePlacesUserID');
if(selectorHide.selectedIndex > 0)
{
let selHideUserName = document.getElementById('_selectHidePlacesUserID').selectedOptions[0].innerHTML;
if(selHideUserName == tbHideUserName)
{
filterHideNameID = document.getElementById('_selectHidePlacesUserID').selectedOptions[0].value;
}
}
filterHideNameID = uroUtils.GetUserID(filterHideNameID, tbHideUserName);
let filterCats = [];
let mainCategories = sdk.DataModel.Venues.getVenueMainCategories();
let subCategories = sdk.DataModel.Venues.getVenueSubCategories();
for(let i = 0; i < mainCategories.length; i++)
{
let parentCategory = mainCategories[i];
let subCategory;
if(uroUtils.GetCBChecked('_cbPlacesFilter-'+parentCategory.id) === true)
{
filterCats.push(parentCategory.id);
for(let i1=0; i1 < subCategories.length; i1++)
{
subCategory = subCategories[i1];
if(subCategory.categoryId === parentCategory.id)
{
filterCats.push(subCategory.subCategoryId);
}
}
}
else
{
for(let i2 = 0; i2 < subCategories.length; i2++)
{
subCategory = subCategories[i2];
if(subCategory.categoryId === parentCategory.id)
{
if(uroUtils.GetCBChecked('_cbPlacesFilter-'+subCategory.subCategoryId) === true)
{
filterCats.push(subCategory.subCategoryId);
}
}
}
}
}
let placeStyle;
let uFP_filterEditedLessThan = uroUtils.GetCBChecked('_cbPlaceFilterEditedLessThan');
let uFP_filterEditedMoreThan = uroUtils.GetCBChecked('_cbPlaceFilterEditedMoreThan');
let uFP_filterL0 = uroUtils.GetCBChecked('_cbHidePlacesL0');
let uFP_filterL1 = uroUtils.GetCBChecked('_cbHidePlacesL1');
let uFP_filterL2 = uroUtils.GetCBChecked('_cbHidePlacesL2');
let uFP_filterL3 = uroUtils.GetCBChecked('_cbHidePlacesL3');
let uFP_filterL4 = uroUtils.GetCBChecked('_cbHidePlacesL4');
let uFP_filterL5 = uroUtils.GetCBChecked('_cbHidePlacesL5');
let uFP_filterStaff = uroUtils.GetCBChecked('_cbHidePlacesStaff');
let uFP_filterAL = uroUtils.GetCBChecked('_cbHidePlacesAdLocked');
let uFP_filterOnLockLevel = (uFP_filterL0 || uFP_filterL1 || uFP_filterL2 || uFP_filterL3 || uFP_filterL4 || uFP_filterL5 || uFP_filterStaff);
let uFP_filterNoPhotos = uroUtils.GetCBChecked('_cbHideNoPhotoPlaces');
let uFP_filterWithPhotos = uroUtils.GetCBChecked('_cbHidePhotoPlaces');
let uFP_filterNoLinks = uroUtils.GetCBChecked('_cbHideNoLinkedPlaces');
let uFP_filterWithLinks = uroUtils.GetCBChecked('_cbHideLinkedPlaces');
let uFP_filterNoDescription = uroUtils.GetCBChecked('_cbHideNonDescribedPlaces');
let uFP_filterWithDescription = uroUtils.GetCBChecked('_cbHideDescribedPlaces');
let uFP_filterNoKeyword = uroUtils.GetCBChecked('_cbHideKeywordPlaces');
let uFP_filterKeyword = uroUtils.GetCBChecked('_cbHideNoKeywordPlaces');
let uFP_filterPrivate = uroUtils.GetCBChecked('_cbFilterPrivatePlaces');
let uFP_invertFilters = uroUtils.GetCBChecked('_cbInvertPlacesFilter');
let uFP_masterEnable = uroUtils.IsFilteringEnabled(false);
let uFP_filterAreaPlaces = uroUtils.GetCBChecked('_cbHideAreaPlaces');
let uFP_filterPointPlaces = uroUtils.GetCBChecked('_cbHidePointPlaces');
let uFP_filterCreatedBy = uroUtils.GetCBChecked('_cbShowOnlyPlacesCreatedBy');
let uFP_filterEditedBy = uroUtils.GetCBChecked('_cbShowOnlyPlacesEditedBy');
let uFP_filterHideCreatedBy = uroUtils.GetCBChecked('_cbHideOnlyPlacesCreatedBy');
let uFP_filterHideEditedBy = uroUtils.GetCBChecked('_cbHideOnlyPlacesEditedBy');
let uFP_hidePURsForFilteredPlaces = uroUtils.GetCBChecked('_cbHidePURsForFilteredPlaces');
let uFP_NameKeyword = document.getElementById('_textKeywordPlace').value.toLowerCase();
let uFP_thresholdMinDays = document.getElementById('_inputFilterPlaceEditMinDays').value;
let uFP_thresholdMaxDays = document.getElementById('_inputFilterPlaceEditMaxDays').value;
uroPURsToHide = [];
for(let v=0; v<uroVenueLayer.features.length; v++)
{
placeStyle = 'visible';
if(uFP_masterEnable === true)
{
let lmFeature = uroVenueLayer.features[v];
let lmObj = sdk.DataModel.Venues.getById({venueId: lmFeature.attributes.wazeFeature.id});
// when an area place is selected, the drag points for editing the place outline now get added as objects into uroVenueLayer.features,
// however none of these objects had the .attributes.repositoryObject property - whilst the devs have now replaced this with the almost
// identical .wazeFeature._wmeObject property, it's unclear if drag points still need to be excluded from this scan, so the check
// remains in place as a "let's just make sure it has it before trying to use it"...
if(lmObj !== null)
{
if(lmObj.id < 0)
{
// don't apply filtering to newly-created places - this allows the user to leave their filtering settings unchanged whilst
// adding a new place which, once saved, would then be hidden...
break;
}
if(uFP_filterAreaPlaces)
{
if(lmObj.geometry.type === "Polygon")
{
placeStyle = 'hidden';
}
}
if(uFP_filterPointPlaces)
{
if(lmObj.geometry.type === "Point")
{
placeStyle = 'hidden';
}
}
if(placeStyle == 'visible')
{
if((uFP_filterEditedLessThan) || (uFP_filterEditedMoreThan))
{
let editDate = lmObj.modificationData.updatedOn;
if(editDate === undefined)
{
// where a place has never been edited since its creation, use the creation date instead...
editDate = lmObj.modificationData.createdOn;
}
if(editDate != null)
{
let editDaysAgo = uroUtils.DateToDays(editDate);
if(uFP_filterEditedLessThan)
{
if(editDaysAgo < uFP_thresholdMinDays)
{
placeStyle = 'hidden';
}
}
if(uFP_filterEditedMoreThan)
{
if(editDaysAgo > uFP_thresholdMaxDays)
{
placeStyle = 'hidden';
}
}
}
}
}
if(placeStyle == 'visible')
{
if(uFP_filterOnLockLevel)
{
let lockLevel = lmObj.lockRank;
if ((uFP_filterL0) && (lockLevel === 0)) placeStyle = 'hidden';
if ((uFP_filterL1) && (lockLevel === 1)) placeStyle = 'hidden';
if ((uFP_filterL2) && (lockLevel === 2)) placeStyle = 'hidden';
if ((uFP_filterL3) && (lockLevel === 3)) placeStyle = 'hidden';
if ((uFP_filterL4) && (lockLevel === 4)) placeStyle = 'hidden';
if ((uFP_filterL5) && (lockLevel === 5)) placeStyle = 'hidden';
if ((uFP_filterStaff) && (lockLevel === 6)) placeStyle = 'hidden';
}
}
if(placeStyle == 'visible')
{
if(uFP_filterAL)
{
if(lmObj.isAdLocked) placeStyle = 'hidden';
}
}
if(placeStyle == 'visible')
{
if(uFP_filterNoPhotos || uFP_filterWithPhotos)
{
let nPhotos = 0;
for(let loop=0; loop<lmObj.images.length; loop++)
{
if(lmObj.images[loop].isApproved) nPhotos++;
}
if((uFP_filterNoPhotos) && (nPhotos === 0)) placeStyle = 'hidden';
if((uFP_filterWithPhotos) && (nPhotos !== 0)) placeStyle = 'hidden';
}
}
if(placeStyle == 'visible')
{
if(uFP_filterNoLinks || uFP_filterWithLinks)
{
let nLinks = lmObj.externalProviderIds.length;
if((uFP_filterNoLinks) && (nLinks === 0)) placeStyle = 'hidden';
if((uFP_filterWithLinks) && (nLinks !== 0)) placeStyle = 'hidden';
}
}
if(placeStyle == 'visible')
{
if(uFP_filterNoDescription || uFP_filterWithDescription)
{
let lDesc = lmObj.description.length;
if((uFP_filterNoDescription) && (lDesc === 0)) placeStyle = 'hidden';
if((uFP_filterWithDescription) && (lDesc !== 0)) placeStyle = 'hidden';
}
}
if(placeStyle == 'visible')
{
if((uFP_filterPrivate === true) && (lmObj.isResidential === true))
{
placeStyle = 'hidden';
}
else
{
for(let cat=0; cat<filterCats.length; cat++)
{
if(_.includes(lmObj.categories, filterCats[cat]))
{
placeStyle = 'hidden';
break;
}
}
}
}
if(placeStyle == 'visible')
{
if(uFP_filterNoKeyword || uFP_filterKeyword)
{
let venueName = lmObj.name.toLowerCase();
let noKeywordMatch = true;
if(uFP_NameKeyword === '')
{
noKeywordMatch = (venueName !== '');
}
else
{
noKeywordMatch = (venueName.indexOf(uFP_NameKeyword) === -1);
}
if(!noKeywordMatch && uFP_filterNoKeyword) placeStyle = 'hidden';
if(noKeywordMatch && uFP_filterKeyword) placeStyle = 'hidden';
}
}
if(placeStyle == 'visible')
{
if(filterNameID != null)
{
if(uFP_filterCreatedBy === true)
{
if(filterNameID != lmObj.modificationData.createdBy) placeStyle = 'hidden';
}
if(uFP_filterEditedBy === true)
{
if(filterNameID != lmObj.modificationData.updatedBy) placeStyle = 'hidden';
}
}
}
if(placeStyle == 'visible')
{
if(filterHideNameID != null)
{
if(uFP_filterHideCreatedBy === true)
{
if(filterHideNameID == lmObj.modificationData.createdBy) placeStyle = 'hidden';
}
if(uFP_filterHideEditedBy === true)
{
if(filterHideNameID == lmObj.modificationData.updatedBy) placeStyle = 'hidden';
}
}
}
if(uFP_invertFilters === true)
{
if(placeStyle == 'hidden') placeStyle = 'visible';
else placeStyle = 'hidden';
}
if((placeStyle == 'hidden') && (uFP_hidePURsForFilteredPlaces === true))
{
uroPURsToHide.push(lmObj.id);
}
}
}
let geoID = uroVenueLayer.features[v].geometry.id;
if(document.getElementById(geoID) !== null)
{
document.getElementById(geoID).style.visibility = placeStyle;
}
}
uro_uFP[uroEnums.FP_OPTS.filterUneditable] = uroUtils.GetCBChecked('_cbFilterUneditablePlaceUpdates');
uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] = uroUtils.GetCBChecked('_cbPURFilterInsideManagedAreas');
uro_uFP[uroEnums.FP_OPTS.excludeMyAreas] = uroUtils.GetCBChecked('_cbPURExcludeUserArea');
uro_uFP[uroEnums.FP_OPTS.filterLockRanked] = uroUtils.GetCBChecked('_cbFilterLockRankedPlaceUpdates');
uro_uFP[uroEnums.FP_OPTS.filterFlagged] = uroUtils.GetCBChecked("_cbFilterFlaggedPUR");
uro_uFP[uroEnums.FP_OPTS.filterNewPlace] = uroUtils.GetCBChecked("_cbFilterNewPlacePUR");
uro_uFP[uroEnums.FP_OPTS.filterUpdatedDetails] = uroUtils.GetCBChecked("_cbFilterUpdatedDetailsPUR");
uro_uFP[uroEnums.FP_OPTS.filterNewPhoto] = uroUtils.GetCBChecked("_cbFilterNewPhotoPUR");
uro_uFP[uroEnums.FP_OPTS.filterMinPURAge] = uroUtils.GetCBChecked('_cbEnablePURMinAgeFilter');
uro_uFP[uroEnums.FP_OPTS.filterMaxPURAge] = uroUtils.GetCBChecked('_cbEnablePURMaxAgeFilter');
uro_uFP[uroEnums.FP_OPTS.invertPURFilters] = uroUtils.GetCBChecked('_cbInvertPURFilters');
uro_uFP[uroEnums.FP_OPTS.leavePURGeos] = uroUtils.GetCBChecked('_cbLeavePURGeos');
uro_uFP[uroEnums.FP_OPTS.filterCFPhone] = uroUtils.GetCBChecked('_cbPURFilterCFPhone');
uro_uFP[uroEnums.FP_OPTS.filterCFName] = uroUtils.GetCBChecked('_cbPURFilterCFName');
uro_uFP[uroEnums.FP_OPTS.filterCFEntryExitPoints] = uroUtils.GetCBChecked('_cbPURFilterCFEntryExitPoints');
uro_uFP[uroEnums.FP_OPTS.filterCFOpeningHours] = uroUtils.GetCBChecked('_cbPURFilterCFOpeningHours');
uro_uFP[uroEnums.FP_OPTS.filterCFAliases] = uroUtils.GetCBChecked('_cbPURFilterCFAliases');
uro_uFP[uroEnums.FP_OPTS.filterCFServices] = uroUtils.GetCBChecked('_cbPURFilterCFServices');
uro_uFP[uroEnums.FP_OPTS.filterCFGeometry] = uroUtils.GetCBChecked('_cbPURFilterCFGeometry');
uro_uFP[uroEnums.FP_OPTS.filterCFHouseNumber] = uroUtils.GetCBChecked('_cbPURFilterCFHouseNumber');
uro_uFP[uroEnums.FP_OPTS.filterCFCategories] = uroUtils.GetCBChecked('_cbPURFilterCFCategories');
uro_uFP[uroEnums.FP_OPTS.filterCFDescription] = uroUtils.GetCBChecked('_cbPURFilterCFDescription');
uro_uFP[uroEnums.FP_OPTS.filterOnCFs] =
(
uro_uFP[uroEnums.FP_OPTS.filterCFPhone] ||
uro_uFP[uroEnums.FP_OPTS.filterCFName] ||
uro_uFP[uroEnums.FP_OPTS.filterCFEntryExitPoints] ||
uro_uFP[uroEnums.FP_OPTS.filterCFOpeningHours]
);
uro_uFP[uroEnums.FP_OPTS.filterOnCFs] =
(
uro_uFP[uroEnums.FP_OPTS.filterOnCFs] ||
uro_uFP[uroEnums.FP_OPTS.filterCFAliases] ||
uro_uFP[uroEnums.FP_OPTS.filterCFServices] ||
uro_uFP[uroEnums.FP_OPTS.filterCFGeometry]
);
uro_uFP[uroEnums.FP_OPTS.filterOnCFs] =
(
uro_uFP[uroEnums.FP_OPTS.filterOnCFs] ||
uro_uFP[uroEnums.FP_OPTS.filterCFHouseNumber] ||
uro_uFP[uroEnums.FP_OPTS.filterCFCategories] ||
uro_uFP[uroEnums.FP_OPTS.filterCFDescription]
);
uro_uFP[uroEnums.FP_OPTS.thresholdMinPURDays] = uroUtils.GetElmValue('_inputPURFilterMinDays');
uro_uFP[uroEnums.FP_OPTS.thresholdMaxPURDays] = uroUtils.GetElmValue('_inputPURFilterMaxDays');
uro_uFP[uroEnums.FP_OPTS.isLoggedIn] = sdk.State.isLoggedIn();
uro_uFP[uroEnums.FP_OPTS.userRank] = sdk.State.getUserInfo().rank;
uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] = uro_uFP[uroEnums.FP_OPTS.filterInsideManagedAreas] && (uroGetManagedAreas() !== 0);
if(uroUtils.GetCBChecked('_cbPURExcludeUserArea') == true)
{
uroIgnoreAreasUserName = sdk.State.getUserInfo()?.userName;
}
uroPrepForFilterPlaceMarker(uroLayersFT.ID.PUR, uFP_masterEnable);
uroPrepForFilterPlaceMarker(uroLayersFT.ID.PPUR, uFP_masterEnable);
uroPrepForFilterPlaceMarker(uroLayersFT.ID.RPUR, uFP_masterEnable);
uroDBG.PerfMon(pmFunction, pmTStart);
}
function uroPrepForFilterPlaceMarker(markerType, masterEnable)
{
if(uroLayers.LayerVisible(uroLayers.layers[markerType].name) === true)
{
let idList = uroGetMarkerIDs(markerType);
for(let pu of idList)
{
let mObj = uroGetMarker(markerType, pu);
if(mObj !== null)
{
uroFilterPlaceMarker(markerType, pu, masterEnable);
}
}
}
}
function uroFilterMapComments()
{
let pmTStart = performance.now();
let pmFunction = "uroFilterMapComments";
if(uroFilterPreamble() === false) return;
let uFURs_masterEnable = uroUtils.IsFilteringEnabled(false);
let filterDescMustBePresent = uroUtils.GetCBChecked('_cbMCDescriptionMustBePresent');
let filterDescMustBeAbsent = uroUtils.GetCBChecked('_cbMCDescriptionMustBeAbsent');
let filterKeywordMustBePresent = uroUtils.GetCBChecked('_cbMCEnableKeywordMustBePresent');
let filterKeywordMustBeAbsent = uroUtils.GetCBChecked('_cbMCEnableKeywordMustBeAbsent');
let filterMyFollowed = uroUtils.GetCBChecked('_cbMCHideMyFollowed');
let filterMyUnfollowed = uroUtils.GetCBChecked('_cbMCHideMyUnfollowed');
let filterRoadworks = uroUtils.GetCBChecked('_cbMCFilterRoadworks');
let filterConstruction = uroUtils.GetCBChecked('_cbMCFilterConstruction');
let filterClosure = uroUtils.GetCBChecked('_cbMCFilterClosure');
let filterEvent = uroUtils.GetCBChecked('_cbMCFilterEvent');
let filterNote = uroUtils.GetCBChecked('_cbMCFilterNote');
let filterWSLM = uroUtils.GetCBChecked('_cbMCFilterWSLM');
let filterBOG = uroUtils.GetCBChecked('_cbMCFilterBOG');
let filterDifficult = uroUtils.GetCBChecked('_cbMCFilterDifficult');
let invertFilters = uroUtils.GetCBChecked('_cbInvertMCFilter');
let keywordPresent = uroUtils.GetElmValue('_textMCKeywordPresent');
let keywordAbsent = uroUtils.GetElmValue('_textMCKeywordAbsent');
let caseInsensitive = uroUtils.GetCBChecked('_cbMCCaseInsensitive');
let filterCommentsMustBePresent = uroUtils.GetCBChecked('_cbMCCommentsMustBePresent');
let filterCommentsMustBeAbsent = uroUtils.GetCBChecked('_cbMCCommentsMustBeAbsent');
let filterExpiryMustBePresent = uroUtils.GetCBChecked('_cbMCExpiryMustBePresent');
let filterExpiryMustBeAbsent = uroUtils.GetCBChecked('_cbMCExpiryMustBeAbsent');
let filterByCreatorEnable = uroUtils.GetCBChecked('_cbMCCreatorIDFilter');
let filterL1 = uroUtils.GetCBChecked('_cbHideMCRank0');
let filterL2 = uroUtils.GetCBChecked('_cbHideMCRank1');
let filterL3 = uroUtils.GetCBChecked('_cbHideMCRank2');
let filterL4 = uroUtils.GetCBChecked('_cbHideMCRank3');
let filterL5 = uroUtils.GetCBChecked('_cbHideMCRank4');
let filterL6 = uroUtils.GetCBChecked('_cbHideMCRank5');
let filterWRCMC = uroUtils.GetCBChecked('_cbHideWRCMCs');
let selectorCreator = document.getElementById('_selectMCCreatorID');
if(filterByCreatorEnable === false)
{
while(selectorCreator.options.length > 0)
{
selectorCreator.options.remove(0);
}
}
let creatorUser = null;
if(filterByCreatorEnable === true)
{
if(selectorCreator.options.length === 0)
{
uroUpdateEditorList(sdk.DataModel.MapComments.getAll(), '_selectMCCreatorID', true, false, false, false);
}
if(selectorCreator.selectedOptions[0] != null)
{
creatorUser = selectorCreator.selectedOptions[0].value;
}
}
let MCs = sdk.DataModel.MapComments.getAll();
for (let mcIdx = 0; mcIdx < MCs.length; mcIdx++)
{
{
let desc = '';
if(MCs[mcIdx].subject !== null) desc += MCs[mcIdx].subject.replace(/<\/?[^>]+(>|$)/g, "");
if(MCs[mcIdx].body !== null) desc += MCs[mcIdx].body.replace(/<\/?[^>]+(>|$)/g, "");
let nComments = MCs[mcIdx].conversation.length;
if(nComments > 0)
{
for(let cIdx=0; cIdx < nComments; cIdx++)
{
desc += MCs[mcIdx].conversation[cIdx].text.replace(/<\/?[^>]+(>|$)/g, "");
}
}
let mcStyle = 'visible';
let mcID = MCs[mcIdx].id;
if(uroIgnore.IsOnList(mcID)) mcStyle = 'hidden';
if(uFURs_masterEnable === true)
{
let ukroadworks_ur = false;
let construction_ur = false;
let closure_ur = false;
let event_ur = false;
let note_ur = false;
let wslm_ur = false;
let bog_ur = false;
let difficult_ur = false;
let filterByNotIncludedKeyword = false;
let filterByIncludedKeyword = true;
let customType = uroGetCustomType(null, "mc", desc);
if(customType === 0) ukroadworks_ur = true;
else if(customType === 1) construction_ur = true;
else if(customType === 2) closure_ur = true;
else if(customType === 3) event_ur = true;
else if(customType === 4) note_ur = true;
else if(customType === 5) wslm_ur = true;
else if(customType === 6) bog_ur = true;
else if(customType === 7) difficult_ur = true;
let rank = MCs[mcIdx].lockRank;
let expiry = MCs[mcIdx].endDate;
// keywords
if(mcStyle == 'visible')
{
if(filterDescMustBePresent === true)
{
if(desc === '') mcStyle = 'hidden';
}
if(filterDescMustBeAbsent === true)
{
if(desc !== '') mcStyle = 'hidden';
}
if(filterCommentsMustBePresent === true)
{
if(nComments === 0) mcStyle = 'hidden';
}
if(filterCommentsMustBeAbsent === true)
{
if(nComments > 0) mcStyle = 'hidden';
}
if(filterKeywordMustBePresent === true)
{
let keywordIsPresentInDesc = uroUtils.KeywordPresent(desc,keywordPresent,caseInsensitive);
filterByIncludedKeyword = (filterByIncludedKeyword && (!keywordIsPresentInDesc));
}
if(filterKeywordMustBeAbsent === true)
{
let keywordIsAbsentInDesc = uroUtils.KeywordPresent(desc,keywordAbsent,caseInsensitive);
filterByNotIncludedKeyword = (filterByNotIncludedKeyword || keywordIsAbsentInDesc);
}
filterByNotIncludedKeyword = (filterByNotIncludedKeyword && filterKeywordMustBeAbsent);
filterByIncludedKeyword = (filterByIncludedKeyword && filterKeywordMustBePresent);
if(filterByNotIncludedKeyword || filterByIncludedKeyword)
{
mcStyle = 'hidden';
}
}
//lock rank
if(mcStyle == 'visible')
{
if((filterL1 === true) && (rank == 0)) mcStyle = 'hidden';
if((filterL2 === true) && (rank == 1)) mcStyle = 'hidden';
if((filterL3 === true) && (rank == 2)) mcStyle = 'hidden';
if((filterL4 === true) && (rank == 3)) mcStyle = 'hidden';
if((filterL5 === true) && (rank == 4)) mcStyle = 'hidden';
if((filterL6 === true) && (rank == 5)) mcStyle = 'hidden';
}
// expiry
if(mcStyle == 'visible')
{
if((filterExpiryMustBePresent === true) && (expiry === null)) mcStyle = 'hidden';
if((filterExpiryMustBeAbsent === true) && (expiry != null)) mcStyle = 'hidden';
}
// is following?
if(mcStyle == 'visible')
{
if(MCs[mcIdx].isFollowing === true)
{
if(filterMyFollowed === true) mcStyle = 'hidden';
}
else
{
if(filterMyUnfollowed === true) mcStyle = 'hidden';
}
}
if(mcStyle == 'visible')
{
if(creatorUser !== null)
{
if(MCs[mcIdx].modificationData.createdBy != creatorUser) mcStyle = 'hidden';
}
if(filterWRCMC === true)
{
if(MCs[mcIdx].modificationData.createdBy == 304740435) mcStyle = 'hidden';
}
}
// custom tags
if(mcStyle == 'visible')
{
if(ukroadworks_ur === true)
{
if(filterRoadworks === true) mcStyle = 'hidden';
}
else if(construction_ur === true)
{
if(filterConstruction === true) mcStyle = 'hidden';
}
else if(closure_ur === true)
{
if(filterClosure === true) mcStyle = 'hidden';
}
else if(event_ur === true)
{
if(filterEvent === true) mcStyle = 'hidden';
}
else if(note_ur === true)
{
if(filterNote === true) mcStyle = 'hidden';
}
else if(wslm_ur === true)
{
if(filterWSLM === true) mcStyle = 'hidden';
}
else if(bog_ur === true)
{
if(filterBOG === true) mcStyle = 'hidden';
}
else if(difficult_ur === true)
{
if(filterDifficult === true) mcStyle = 'hidden';
}
if(invertFilters === true)
{
if(mcStyle == 'hidden') mcStyle = 'visible';
else mcStyle = 'hidden';
}
}
}
let mcFeatureObj = uroMapComments.GetMCFeatureByID(mcID);
if(mcFeatureObj !== null)
{
let geoID = mcFeatureObj.geometry.id;
if(document.getElementById(geoID) !== null)
{
document.getElementById(geoID).style.visibility = mcStyle;
}
}
}
}
uroDBG.PerfMon(pmFunction, pmTStart);
}
function uroFilterURs_onObjectsChanged()
{
if(uroFilterPreamble())
{
if(uroURDialogIsOpen === true)
{
uroFilterURs();
}
}
}
function uroFilterURs_onObjectsAdded()
{
if(uroFilterPreamble())
{
}
}
function uroFilterURs_onObjectsRemoved()
{
if(uroFilterPreamble())
{
}
}
function uroGetManagedAreas()
{
uroManagedAreas = [];
uroIgnoreAreasUserName = undefined;
let managedAreas = sdk.DataModel.ManagedAreas.getAll();
for(let maObj in managedAreas)
{
uroManagedAreas.push(managedAreas[maObj]);
}
return uroManagedAreas.length;
}
function uroCheckGeometryWithinManagedAreas(geo)
{
let retval = false;
let ignoreUserMA = false;
// If we're ignoring the user's managed area, then we first check to see if
// the geopoint lies within that - if so then we can skip checking all the
// other areas in the list...
if(uroIgnoreAreasUserName !== undefined)
{
for(let uma = 0; uma < uroManagedAreas.length; ++uma)
{
if(uroIgnoreAreasUserName == uroManagedAreas[uma].userName)
{
ignoreUserMA = uroContainsPoint(uroManagedAreas[uma].geometry.coordinates[0], geo);
break;
}
}
}
// Point either isn't within the user's area, or we're not ignoring it, so
// check the rest of the areas in the list
if(ignoreUserMA == false)
{
for(let ma = 0; ma < uroManagedAreas.length; ++ma)
{
if(uroIgnoreAreasUserName != uroManagedAreas[ma].userName)
{
retval = uroContainsPoint(uroManagedAreas[ma].geometry.coordinates[0], geo);
break;
}
}
}
return retval;
}
function uroGetURDriveGeoms()
{
let retval = [];
for (let urobj in W.model.mapUpdateRequests.objects)
{
if(W.model.mapUpdateRequests.objects.hasOwnProperty(urobj))
{
let ureq = W.model.mapUpdateRequests.objects[urobj];
let ureqID = ureq.attributes.id;
let hasGeo = false;
let thisRet = [];
thisRet.push(ureqID);
thisRet.push(null);
thisRet.push([]);
let latMin = 9999;
let latMax = -9999;
let lonMin = 9999;
let lonMax = -9999;
let sdObj = uroURSessions.GetSessionData(ureqID);
if((sdObj !== null) && (sdObj.driveGeometry !== null))
{
let cPairs = [];
for(let i = 0; i < sdObj.driveGeometry.coordinates.length; ++i)
{
for(let j = 0; j < sdObj.driveGeometry.coordinates[i].length; ++j)
{
if((i === 0) || (j > 0))
{
let coords = sdObj.driveGeometry.coordinates[i][j];
cPairs.push(coords);
if(coords[0] > lonMax)
{
lonMax = coords[0];
}
if(coords[0] < lonMin)
{
lonMin = coords[0];
}
if(coords[1] > latMax)
{
latMax = coords[1];
}
if(coords[1] < latMin)
{
latMin = coords[1];
}
hasGeo = true;
}
}
}
let bbox = [];
bbox.push(lonMin);
bbox.push(lonMax);
bbox.push(latMin);
bbox.push(latMax);
thisRet.push(bbox);
thisRet.push(cPairs);
}
if(hasGeo === true)
{
retval.push(thisRet);
}
}
}
return retval;
}
function uroCompareDriveGeos(geoA, geoB)
{
const matchLength = 5;
let retval = false;
if((geoA.length >= matchLength) && (geoB.length >= matchLength))
{
for(let i = 0; i < (geoA.length - matchLength); ++i)
{
for(let j = 0; j < (geoB.length - matchLength); ++j)
{
if((geoA[i][0] == geoB[j][0]) && (geoA[i][1] == geoB[j][1]))
{
retval = true;
for(let k = 1; k < matchLength; ++k)
{
if((geoA[i+k][0] != geoB[j+k][0]) || (geoA[i+k][1] != geoB[j+k][1]))
{
retval = false;
break;
}
}
}
if(retval === true)
{
break;
}
}
if(retval === true)
{
break;
}
}
}
return retval;
}
function uroCompareDriveBBoxes(bbA, bbB)
{
let retval = true;
if
(
(bbA[0] > bbB[1]) ||
(bbA[1] < bbB[0]) ||
(bbA[2] > bbB[3]) ||
(bbA[3] < bbB[2])
)
{
retval = false;
}
return retval;
}
function uroGetURDupes()
{
uroURDupes = [];
// To determine which URs are duplicates of one another (i.e. have been raised by the same user
// during the same section of a journey), we first compare the geometries of the drive tracks
// attached to any URs which have them - as this is based on the users GPS position rather than
// the WME map data, it makes it vanishingly unlikely that any two users would have identical
// GPS positions (especially given the level of accuracy to which the track points are stored)
// even if they were driving exactly the same route at the same speed, in the same lane etc.
//
// To accelerate this geometry comparison, we start by performing a simple bounding box overlap
// check for the two geometries under consideration - if there's no overlap then there can't be
// any geometry match, so no need to continue onto the more detailed comparision of the GPS
// tracks themselves...
let driveGeos = uroGetURDriveGeoms();
if(driveGeos.length > 1)
{
for(let i = 0; i < (driveGeos.length - 1); ++i)
{
if(driveGeos[i].length !== 5)
{
driveGeos[i][1] = false;
}
else
{
for(let j = (i + 1); j < driveGeos.length; ++j)
{
if(driveGeos[j].length === 5)
{
let geoMatch = uroCompareDriveBBoxes(driveGeos[i][3], driveGeos[j][3]);
if(geoMatch === true)
{
geoMatch = uroCompareDriveGeos(driveGeos[i][4], driveGeos[j][4]);
if(geoMatch === true)
{
driveGeos[i][2].push(driveGeos[j][0]);
driveGeos[j][2].push(driveGeos[i][0]);
}
}
}
}
}
}
for(let i = 0; i < driveGeos.length; ++i)
{
if(driveGeos[i][2].length > 0)
{
let res = [];
res.push(driveGeos[i][0]);
res.push(driveGeos[i][2]);
uroURDupes.push(res);
}
}
}
// Once we've done the initial drive track comparision, uroURDupes will contain a list of
// all the URs which were matched up based on that. However, as the track comparision is
// inherently limited by the amount of track data included with each UR, this initial
// comparison may mean some more widely spaced URs aren't flagged as duplicates simply
// because there's insufficient overlap between their tracks, even though there may have
// been further URs dropped inbetween to which they were matched.
//
// e.g. if a user drops 4 URs along a section of their journey, spaced such that each of
// the GPS tracks generates a comparison match with the UR either side of it, but no
// further than that, we would get:
//
// UR A......UR B.......UR C......UR D
//
// Matches: A to B, B to A & C, C to B & D, D to C
//
// Note how, although we know all of these URs are in fact duplicates, and can infer this
// from seeing that e.g. A is flagged only as a duplicate of B, however as B is flagged as
// a duplicate of A & C, and C is flagged as a duplicate of B & D, A MUST therefore be a
// duplicate of B, C & D...
//
// To fix this, we now run a merging pass over each of the entries in uroURDupes - for
// each entry we see if its ID appears in any of the other entries as a duplicate, and
// if so we merge the duplicates for both entries.
for(let i = 0; i < uroURDupes.length; ++i)
{
let urID = uroURDupes[i][0];
for(let j = 0; j < uroURDupes.length; ++j)
{
if(i != j)
{
if(uroURDupes[j][1].includes(urID) === true)
{
// https://stackoverflow.com/questions/1584370/how-to-merge-two-arrays-in-javascript-and-de-duplicate-items
let mergedDupes = [...new Set([...uroURDupes[i][1], ...uroURDupes[j][1]])];
uroURDupes[i][1] = mergedDupes;
uroURDupes[j][1] = mergedDupes;
}
}
}
}
}
function uroFilterURs()
{
let pmTStart = performance.now();
let pmFunction = "uroFilterURs";
if(uroUserName === null)
{
return;
}
// compatibility fix for URComments - based on code supplied by RickZabel
let hasActiveURFilters = false;
if(uroUtils.IsFilteringEnabled(false) === true)
{
let urTabInputs = uroTabs.CtrlTabs[uroTabs.IDS.URS][uroTabs.FIELDS.TABBODY].getElementsByTagName('input');
for(let loop = 0; loop < urTabInputs.length; loop++)
{
if(urTabInputs[loop].type == 'checkbox')
{
let ignoreCB = false;
ignoreCB = ignoreCB || (urTabInputs[loop].id == '_cbCaseInsensitive');
ignoreCB = ignoreCB || (urTabInputs[loop].id == '_cbNoFilterForTaggedURs');
if((urTabInputs[loop].checked) && (ignoreCB === false))
{
hasActiveURFilters = true;
break;
}
}
}
}
sessionStorage.UROverview_hasActiveURFilters = hasActiveURFilters;
if(uroFilterPreamble() === false) return;
let selectorResolver = document.getElementById('_selectURResolverID');
let selectorCommentUser = document.getElementById('_selectURUserID');
if(uroUtils.GetCBChecked('_cbURResolverIDFilter') === false)
{
while(selectorResolver.options.length > 0)
{
selectorResolver.options.remove(0);
}
}
if(uroUtils.GetCBChecked('_cbURUserIDFilter') === false)
{
while(selectorCommentUser.options.length > 0)
{
selectorCommentUser.options.remove(0);
}
}
if(uroURSessions.GetSessionDataLength() === 0)
{
// This may be the case if the user has disabled the UR layer, so call
// AddCommentCounts to clear any existing ones from the map view...
uroURExtras.AddCommentCounts();
return;
}
let commenterUser = null;
if(uroUtils.GetCBChecked('_cbURUserIDFilter') === true)
{
if(selectorCommentUser.options.length === 0)
{
uroUpdateEditorList(uroURSessions.sessionData, '_selectURUserID', false, false, false, true);
}
if(selectorCommentUser.selectedOptions[0] != null)
{
commenterUser = parseInt(selectorCommentUser.selectedOptions[0].value);
}
}
let resolverUser = null;
if(uroUtils.GetCBChecked('_cbURResolverIDFilter') === true)
{
if(selectorResolver.options.length === 0)
{
uroUpdateEditorList(W.model.mapUpdateRequests.objects, '_selectURResolverID', false, false, true, false);
}
if(selectorResolver.selectedOptions[0] != null)
{
resolverUser = parseInt(selectorResolver.selectedOptions[0].value);
}
}
uroURExtras.urList = [];
uroGetURDupes();
let uFURs_masterEnable = uroUtils.IsFilteringEnabled(false);
let filterOutsideEditableArea = uroUtils.GetCBChecked('_cbURFilterOutsideArea');
let filterInsideManagedAreas = uroUtils.GetCBChecked('_cbURFilterInsideManagedAreas');
let filterSolved = uroUtils.GetCBChecked('_cbFilterSolved');
let filterUnidentified = uroUtils.GetCBChecked('_cbFilterUnidentified');
let filterClosed = uroUtils.GetCBChecked('_cbFilterClosedUR');
let filterOpen = uroUtils.GetCBChecked('_cbFilterOpenUR');
let filterDescMustBePresent = uroUtils.GetCBChecked('_cbURDescriptionMustBePresent');
let filterDescMustBeAbsent = uroUtils.GetCBChecked('_cbURDescriptionMustBeAbsent');
let filterKeywordMustBePresent = uroUtils.GetCBChecked('_cbEnableKeywordMustBePresent');
let filterKeywordMustBeAbsent = uroUtils.GetCBChecked('_cbEnableKeywordMustBeAbsent');
let filterMinURAge = uroUtils.GetCBChecked('_cbEnableMinAgeFilter');
let filterMaxURAge = uroUtils.GetCBChecked('_cbEnableMaxAgeFilter');
let filterMinComments = uroUtils.GetCBChecked('_cbEnableMinCommentsFilter');
let filterMaxComments = uroUtils.GetCBChecked('_cbEnableMaxCommentsFilter');
let filterReporterLastCommenter = uroUtils.GetCBChecked('_cbHideIfReporterLastCommenter');
let filterReporterNotLastCommenter = uroUtils.GetCBChecked('_cbHideIfReporterNotLastCommenter');
let filterHideAnyComments = uroUtils.GetCBChecked('_cbHideAnyComments');
let filterHideNotLastCommenter = uroUtils.GetCBChecked('_cbHideIfNotLastCommenter');
let filterHideMyComments = uroUtils.GetCBChecked('_cbHideMyComments');
let filterIfLastCommenter = uroUtils.GetCBChecked('_cbHideIfLastCommenter');
let filterIfNotLastCommenter = uroUtils.GetCBChecked('_cbHideIfNotLastCommenter');
let filterCommentMinAge = uroUtils.GetCBChecked('_cbEnableCommentAgeFilter2');
let filterCommentMaxAge = uroUtils.GetCBChecked('_cbEnableCommentAgeFilter');
let filterUserID = uroUtils.GetCBChecked('_cbURUserIDFilter');
let filterMyFollowed = uroUtils.GetCBChecked('_cbHideMyFollowed');
let filterMyUnfollowed = uroUtils.GetCBChecked('_cbHideMyUnfollowed');
let filterWazeAuto = uroUtils.GetCBChecked('_cbFilterWazeAuto');
let filterRoadworks = uroUtils.GetCBChecked('_cbFilterRoadworks');
let filterConstruction = uroUtils.GetCBChecked('_cbFilterConstruction');
let filterClosure = uroUtils.GetCBChecked('_cbFilterClosure');
let filterEvent = uroUtils.GetCBChecked('_cbFilterEvent');
let filterNote = uroUtils.GetCBChecked('_cbFilterNote');
let filterWSLM = uroUtils.GetCBChecked('_cbFilterWSLM');
let filterBOG = uroUtils.GetCBChecked('_cbFilterBOG');
let filterDifficult = uroUtils.GetCBChecked('_cbFilterDifficult');
let filterIncorrectTurn = uroUtils.GetCBChecked('_cbFilterIncorrectTurn');
let filterIncorrectAddress = uroUtils.GetCBChecked('_cbFilterIncorrectAddress');
let filterIncorrectRoute = uroUtils.GetCBChecked('_cbFilterIncorrectRoute');
let filterMissingRoundabout = uroUtils.GetCBChecked('_cbFilterMissingRoundabout');
let filterGeneralError = uroUtils.GetCBChecked('_cbFilterGeneralError');
let filterTurnNotAllowed = uroUtils.GetCBChecked('_cbFilterTurnNotAllowed');
let filterIncorrectJunction = uroUtils.GetCBChecked('_cbFilterIncorrectJunction');
let filterMissingBridgeOverpass = uroUtils.GetCBChecked('_cbFilterMissingBridgeOverpass');
let filterWrongDrivingDirection = uroUtils.GetCBChecked('_cbFilterWrongDrivingDirection');
let filterMissingExit = uroUtils.GetCBChecked('_cbFilterMissingExit');
let filterMissingRoad = uroUtils.GetCBChecked('_cbFilterMissingRoad');
let filterMissingLandmark = uroUtils.GetCBChecked('_cbFilterMissingLandmark');
let filterNativeSpeedLimit = uroUtils.GetCBChecked('_cbFilterSpeedLimits');
let filterBlockedRoad = uroUtils.GetCBChecked('_cbFilterBlockedRoad');
let filterUndefined = uroUtils.GetCBChecked('_cbFilterUndefined');
let invertAttributesFilters = uroUtils.GetCBChecked('_cbInvertURAttributesFilter');
let filterOS_IOS = uroUtils.GetCBChecked('_cbFilterOS_IOS');
let filterOS_Android = uroUtils.GetCBChecked('_cbFilterOS_Android');
let filterOS_AndroidAuto = uroUtils.GetCBChecked('_cbFilterOS_AndroidAuto');
let filterOS_CarPlay = uroUtils.GetCBChecked('_cbFilterOS_CarPlay');
let filterOS_Unknown = uroUtils.GetCBChecked('_cbFilterOS_Unknown');
let filterSource_MobileClient = uroUtils.GetCBChecked('_cbFilterSource_MobileClient');
let filterSource_Web = uroUtils.GetCBChecked('_cbFilterSource_Web');
let filterSource_MobileWeb = uroUtils.GetCBChecked('_cbFilterSource_MobileWeb');
let filterSource_Unknown = uroUtils.GetCBChecked('_cbFilterSource_Unknown');
let filterIsEV_True = uroUtils.GetCBChecked('_cbFilterIsEV_True');
let filterIsEV_False = uroUtils.GetCBChecked('_cbFilterIsEV_False');
let filterIsEV_Unknown = uroUtils.GetCBChecked('_cbFilterIsEV_Unknown');
let filterVehicle_Private = uroUtils.GetCBChecked('_cbFilterVehicle_Private');
let filterVehicle_Taxi = uroUtils.GetCBChecked('_cbFilterVehicle_Taxi');
let filterVehicle_Motorcycle = uroUtils.GetCBChecked('_cbFilterVehicle_Motorcycle');
let filterVehicle_Unknown = uroUtils.GetCBChecked('_cbFilterVehicle_Unknown');
let invertURFilters = uroUtils.GetCBChecked('_cbInvertURFilter');
let invertURStateFilters = uroUtils.GetCBChecked('_cbInvertURStateFilter');
let noFilterTaggedURs = uroUtils.GetCBChecked('_cbNoFilterForTaggedURs');
let noFilterURInURL = uroUtils.GetCBChecked('_cbNoFilterForURInURL');
let showOnlyDupes = uroUtils.GetCBChecked('_cbURFilterDupes');
let keywordPresent = uroUtils.GetElmValue('_textKeywordPresent');
let keywordAbsent = uroUtils.GetElmValue('_textKeywordAbsent');
let caseInsensitive = uroUtils.GetCBChecked('_cbCaseInsensitive');
let thresholdMinAge = uroUtils.GetElmValue('_inputFilterMinDays');
let thresholdMaxAge = uroUtils.GetElmValue('_inputFilterMaxDays');
let thresholdMinComments = uroUtils.GetElmValue('_inputFilterMinComments');
let thresholdMaxComments = uroUtils.GetElmValue('_inputFilterMaxComments');
let thresholdMaxCommentAge = uroUtils.GetElmValue('_inputFilterCommentDays');
let thresholdMinCommentAge = uroUtils.GetElmValue('_inputFilterCommentDays2');
let ignoreOtherEditorComments = uroUtils.GetCBChecked('_cbIgnoreOtherEditorComments');
let urcFilteringIsActive = false;
let urcCB = document.getElementById('URCommentsFilterEnabled');
if(urcCB !== null)
{
if(urcCB.checked)
{
urcFilteringIsActive = true;
}
}
urcCB = document.getElementById('URCommentUROOnlyMyUR');
if(urcCB !== null)
{
if(urcCB.checked)
{
urcFilteringIsActive = true;
}
}
urcCB = document.getElementById('URCommentUROHideTagged');
if(urcCB !== null)
{
if(urcCB.checked)
{
urcFilteringIsActive = true;
}
}
filterInsideManagedAreas = filterInsideManagedAreas && (uroGetManagedAreas() !== 0);
if(uroUtils.GetCBChecked('_cbURExcludeUserArea') == true)
{
uroIgnoreAreasUserName = sdk.State.getUserInfo()?.userName;
}
for (let urobj in W.model.mapUpdateRequests.objects)
{
if(W.model.mapUpdateRequests.objects.hasOwnProperty(urobj))
{
let ureq = W.model.mapUpdateRequests.objects[urobj];
let ureqID = ureq.attributes.id;
let urStyle = 'visible';
let inhibitFiltering = ((ureqID == uroSelectedURID) && (noFilterURInURL));
let hasMyComments = false;
let nComments = 0;
let desc = ureq.attributes.description;
let customType = uroGetCustomType(ureqID, uroLayersFT.ID.UR, desc);
let ageLastComment = null;
let sdObj = uroURSessions.GetSessionData(ureqID);
if(sdObj !== null)
{
nComments = sdObj.comments.length;
if(nComments != 0)
{
ageLastComment = uroUtils.GetCommentAge(sdObj.comments[nComments-1]);
}
if((uFURs_masterEnable === false) && (nComments === 0))
{
// when master enable is turned off, we want to make sure that all URs, including ones that were previously hidden, are correctly
// displayed in their native form - i.e. no comment count or custom conversation bubbles. The easiest way to achieve this is to
// force the AddCommentCounts code to test for the presence of these bubbles on each UR, which we do by setting a non-zero
// comment count for each UR... For URs which genuinely do have no comments we use -1 to indicate that we're not really setting
// a comment count, but that we still need to do something that wouldn't be achieved by using 0.
nComments = -1;
}
}
// check UR against current session ignore list...
if(uroIgnore.IsOnList(ureqID)) urStyle = 'hidden';
if((uFURs_masterEnable === true) && (inhibitFiltering === false))
{
let wazeauto_ur = false;
let ukroadworks_ur = false;
let construction_ur = false;
let closure_ur = false;
let event_ur = false;
let note_ur = false;
let wslm_ur = false;
let bog_ur = false;
let difficult_ur = false;
let filterByNotIncludedKeyword = false;
let filterByIncludedKeyword = true;
if(desc !== null) desc = desc.replace(/<\/?[^>]+(>|$)/g, "");
else desc = '';
if(customType === 0) ukroadworks_ur = true;
else if(customType === 1) construction_ur = true;
else if(customType === 2) closure_ur = true;
else if(customType === 3) event_ur = true;
else if(customType === 4) note_ur = true;
else if(customType === 5) wslm_ur = true;
else if(customType === 6) bog_ur = true;
else if(customType === 7) difficult_ur = true;
// check UR against editable area...
if(filterOutsideEditableArea === true)
{
if(ureq.canEdit() === false) urStyle = 'hidden';
}
if(filterInsideManagedAreas === true)
{
if(uroCheckGeometryWithinManagedAreas(ureq.attributes.geoJSONGeometry.coordinates) === true) urStyle = 'hidden';
}
if(showOnlyDupes === true)
{
let isDupe = false;
for(let i = 0; i < uroURDupes.length; ++i)
{
if(uroURDupes[i][0] === ureqID)
{
isDupe = true;
break;
}
}
if(isDupe === false) urStyle = 'hidden';
}
// attribute filtering
let hideOnAttributes = false;
let attrVal = ureq.attributes.os;
hideOnAttributes ||= ((filterOS_IOS === true) && (attrVal === 'IOS'));
hideOnAttributes ||= ((filterOS_Android === true) && (attrVal === 'ANDROID_MOBILE'));
hideOnAttributes ||= ((filterOS_CarPlay === true) && (attrVal === 'CARPLAY'));
hideOnAttributes ||= ((filterOS_AndroidAuto === true) && (attrVal === 'ANDROID_AUTO'));
hideOnAttributes ||= ((filterOS_Unknown === true) && ((attrVal === null) || (attrVal === undefined)));
attrVal = ureq.attributes.source;
hideOnAttributes ||= ((filterSource_MobileClient === true) && (attrVal === 'MOBILE_CLIENT'));
hideOnAttributes ||= ((filterSource_Web === true) && (attrVal === 'WEB'));
hideOnAttributes ||= ((filterSource_MobileWeb === true) && (attrVal === 'MOBILE_WEB'));
hideOnAttributes ||= ((filterSource_Unknown === true) && ((attrVal === null) || (attrVal === undefined)));
attrVal = ureq.attributes.hasEv;
hideOnAttributes ||= ((filterIsEV_True === true) && (attrVal === true));
hideOnAttributes ||= ((filterIsEV_False === true) && (attrVal === false));
hideOnAttributes ||= ((filterIsEV_Unknown === true) && ((attrVal === null) || (attrVal === undefined)));
attrVal = ureq.attributes.vehicleType;
hideOnAttributes ||= ((filterVehicle_Private === true) && (attrVal === 'PRIVATE'));
hideOnAttributes ||= ((filterVehicle_Taxi === true) && (attrVal === 'TAXI'));
hideOnAttributes ||= ((filterVehicle_Motorcycle === true) && (attrVal === 'MOTORCYCLE'));
hideOnAttributes ||= ((filterVehicle_Unknown === true) && ((attrVal === null) || (attrVal === undefined)));
if
(
((hideOnAttributes === true) && (invertAttributesFilters === false)) ||
((hideOnAttributes === false) && (invertAttributesFilters === true))
)
{
urStyle = 'hidden';
}
// state-age filtering
if(urStyle == 'visible')
{
// check against closed/not identified filtering if enabled...
if(filterSolved === true)
{
if(ureq.attributes.resolution === 0) urStyle = 'hidden';
}
if(filterUnidentified === true)
{
if(ureq.attributes.resolution == 1) urStyle = 'hidden';
}
if((ureq.attributes.resolvedOn !== null) && (filterClosed === true))
{
urStyle = 'hidden';
}
if((ureq.attributes.resolvedOn === null) && (filterOpen === true))
{
urStyle = 'hidden';
}
if(urStyle == 'visible')
{
// check UR against keyword filtering if enabled...
if(filterDescMustBePresent === true)
{
if(desc === '') urStyle = 'hidden';
}
if(filterDescMustBeAbsent === true)
{
if(desc !== '') urStyle = 'hidden';
}
if(filterKeywordMustBePresent === true)
{
let keywordIsPresentInDesc = uroUtils.KeywordPresent(desc,keywordPresent,caseInsensitive);
filterByIncludedKeyword = (filterByIncludedKeyword && (!keywordIsPresentInDesc));
}
if(filterKeywordMustBeAbsent === true)
{
let keywordIsAbsentInDesc = uroUtils.KeywordPresent(desc,keywordAbsent,caseInsensitive);
filterByNotIncludedKeyword = (filterByNotIncludedKeyword || keywordIsAbsentInDesc);
}
}
if(urStyle == 'visible')
{
// do age-based filtering if enabled
if(filterMinURAge === true)
{
if(uroUtils.GetURAge(ureq,0,false) < thresholdMinAge) urStyle = 'hidden';
}
if(filterMaxURAge === true)
{
if(uroUtils.GetURAge(ureq,0,false) > thresholdMaxAge) urStyle = 'hidden';
}
}
if(urStyle == 'visible')
{
if(resolverUser !== null)
{
if(ureq.attributes.resolvedBy != resolverUser) urStyle = 'hidden';
}
}
if(urStyle == 'visible')
{
// do comments/following filtering
let sdObj = uroURSessions.GetSessionData(ureqID);
if(sdObj != null)
{
nComments = sdObj.comments.length;
let commentDaysOld = -1;
if(filterMinComments === true)
{
if(nComments < thresholdMinComments) urStyle = 'hidden';
}
if(filterMaxComments === true)
{
if(nComments > thresholdMaxComments) urStyle = 'hidden';
}
if(nComments > 0)
{
let reporterIsLastCommenter = false;
if(sdObj.comments[nComments-1].userName == "") reporterIsLastCommenter = true;
if(filterReporterLastCommenter === true)
{
if(reporterIsLastCommenter === true) urStyle = 'hidden';
}
else if(filterReporterNotLastCommenter === true)
{
if(reporterIsLastCommenter === false) urStyle = 'hidden';
}
hasMyComments = uroURHasMyComments(ureqID);
if(hasMyComments === false)
{
if(filterHideAnyComments === true) urStyle = 'hidden';
if(filterHideNotLastCommenter === true) urStyle = 'hidden';
}
else
{
if(filterHideMyComments === true) urStyle = 'hidden';
let userIsLastCommenter = false;
if(sdObj.comments[nComments-1].userName == uroUserName) userIsLastCommenter = true;
if(filterIfLastCommenter === true)
{
if(userIsLastCommenter === true) urStyle = 'hidden';
}
else if(filterIfNotLastCommenter === true)
{
if(userIsLastCommenter === false) urStyle = 'hidden';
}
}
let cidx;
if(ignoreOtherEditorComments === false)
{
commentDaysOld = ageLastComment;
}
else
{
for(cidx=0; cidx<nComments; cidx++)
{
let cObj = sdObj.comments[cidx];
if((cObj.userID == uroUserID) || (cObj.userID == -1))
{
commentDaysOld = uroUtils.GetCommentAge(cObj);
}
}
}
if((filterCommentMinAge === true) && (commentDaysOld != -1))
{
if(thresholdMinCommentAge > commentDaysOld) urStyle = 'hidden';
}
if((filterCommentMaxAge === true) && (commentDaysOld != -1))
{
if(thresholdMaxCommentAge < commentDaysOld) urStyle = 'hidden';
}
if((commenterUser !== null) && (urStyle != 'hidden'))
{
urStyle = 'hidden';
for(cidx=0; cidx<nComments; cidx++)
{
if(sdObj.comments[cidx].userID == commenterUser)
{
urStyle = 'visible';
break;
}
}
}
let commentText = '';
for(cidx=0; cidx<nComments; cidx++)
{
commentText += sdObj.comments[cidx].text;
}
if(filterKeywordMustBePresent === true)
{
let keywordIsPresentInComments = uroUtils.KeywordPresent(commentText,keywordPresent,caseInsensitive);
filterByIncludedKeyword = (filterByIncludedKeyword && (!keywordIsPresentInComments));
}
if(filterKeywordMustBeAbsent === true)
{
let keywordIsAbsentInComments = uroUtils.KeywordPresent(commentText,keywordAbsent,caseInsensitive);
filterByNotIncludedKeyword = (filterByNotIncludedKeyword || keywordIsAbsentInComments);
}
}
else
{
if(filterUserID === true)
{
urStyle = 'hidden';
}
}
filterByNotIncludedKeyword = (filterByNotIncludedKeyword && filterKeywordMustBeAbsent);
filterByIncludedKeyword = (filterByIncludedKeyword && filterKeywordMustBePresent);
if(filterByNotIncludedKeyword || filterByIncludedKeyword)
{
urStyle = 'hidden';
}
}
}
if(ureq.attributes.isStarred === true)
{
if(filterMyFollowed === true) urStyle = 'hidden';
}
else
{
if(filterMyUnfollowed === true) urStyle = 'hidden';
}
if(invertURStateFilters === true)
{
if(urStyle == 'hidden') urStyle = 'visible';
else urStyle = 'hidden';
}
}
// type filtering
if(urStyle == 'visible')
{
// Test for Waze automatic URs before any others - these always (?) get inserted as General Error URs,
// so we can't filter them by type...
if(desc.indexOf('Waze Automatic:') != -1)
{
wazeauto_ur = true;
}
if(wazeauto_ur === true)
{
if(filterWazeAuto === true) urStyle = 'hidden';
}
else if(ukroadworks_ur === true)
{
if(filterRoadworks === true) urStyle = 'hidden';
}
else if(construction_ur === true)
{
if(filterConstruction === true) urStyle = 'hidden';
}
else if(closure_ur === true)
{
if(filterClosure === true) urStyle = 'hidden';
}
else if(event_ur === true)
{
if(filterEvent === true) urStyle = 'hidden';
}
else if(note_ur === true)
{
if(filterNote === true) urStyle = 'hidden';
}
else if(wslm_ur === true)
{
if(filterWSLM === true) urStyle = 'hidden';
}
else if(bog_ur === true)
{
if(filterBOG === true) urStyle = 'hidden';
}
else if(difficult_ur === true)
{
if(filterDifficult === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 6)
{
if(filterIncorrectTurn === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 7)
{
if (filterIncorrectAddress === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 8)
{
if(filterIncorrectRoute === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 9)
{
if(filterMissingRoundabout === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 10)
{
if(filterGeneralError === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 11)
{
if(filterTurnNotAllowed === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 12)
{
if(filterIncorrectJunction === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 13)
{
if(filterMissingBridgeOverpass === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 14)
{
if(filterWrongDrivingDirection === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 15)
{
if(filterMissingExit === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 16)
{
if(filterMissingRoad === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 18)
{
if(filterMissingLandmark === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 19)
{
if(filterBlockedRoad === true) urStyle = 'hidden';
}
else if(ureq.attributes.type == 23)
{
if(filterNativeSpeedLimit === true) urStyle = 'hidden';
}
else if(filterUndefined === true) urStyle = 'hidden';
if(invertURFilters === true)
{
if(urStyle == 'hidden') urStyle = 'visible';
else urStyle = 'hidden';
}
}
// state-age filtering override for tagged URs
if(noFilterTaggedURs === true)
{
if(ukroadworks_ur === true)
{
if(filterRoadworks === false) urStyle = 'visible';
}
else if(construction_ur === true)
{
if(filterConstruction === false) urStyle = 'visible';
}
else if(closure_ur === true)
{
if(filterClosure === false) urStyle = 'visible';
}
else if(event_ur === true)
{
if(filterEvent === false) urStyle = 'visible';
}
else if(note_ur === true)
{
if(filterNote === false) urStyle = 'visible';
}
else if(wslm_ur === true)
{
if(filterWSLM === false) urStyle = 'visible';
}
}
}
// only touch marker visibility if we've got active filter settings, or if URComments is not
// doing any filtering of its own
if((hasActiveURFilters === true) || (urcFilteringIsActive === false) || (uFURs_masterEnable === false))
{
let urMarker = uroGetMarker(uroLayersFT.ID.UR,urobj);
if(urMarker !== null)
{
urMarker.style.visibility = urStyle;
}
}
if(urStyle != 'hidden')
{
uroURExtras.AddToList(ureqID, customType, hasMyComments, nComments, ageLastComment);
}
}
}
uroURExtras.AddCommentCounts();
uroDBG.PerfMon(pmFunction, pmTStart);
}
function uroGetProblemTypes()
{
uroKnownProblemTypeIDs = [];
uroKnownProblemTypeNames = [];
let tProblemList = I18n.lookup("problems.types");
for(let tObj in tProblemList)
{
if(tObj !== undefined)
{
uroKnownProblemTypeIDs.push(parseInt(tObj));
uroKnownProblemTypeNames.push(tProblemList[tObj].title);
}
}
}
function uroFilterProblems()
{
let pmTStart = performance.now();
let pmFunction = "uroFilterProblems";
if(uroFilterPreamble() === false) return;
let selector;
if((uroUtils.GetCBChecked('_cbMPNotClosedUserIDFilter') === false) && (uroUtils.GetCBChecked('_cbMPClosedUserIDFilter') === false))
{
selector = document.getElementById('_selectMPUserID');
while(selector.options.length > 0)
{
selector.options.remove(0);
}
}
let solverUser = null;
if((uroUtils.GetCBChecked('_cbMPNotClosedUserIDFilter') === true) || (uroUtils.GetCBChecked('_cbMPClosedUserIDFilter') === true))
{
selector = document.getElementById('_selectMPUserID');
if(selector.options.length === 0)
{
uroUpdateEditorList(W.model.mapProblems.objects, '_selectMPUserID', false, false, true, false);
}
if(selector.selectedOptions[0] != null)
{
solverUser = parseInt(selector.selectedOptions[0].value);
}
}
let uFP_masterEnable = uroUtils.IsFilteringEnabled(false);
let filter_OutsideEditableArea = uroUtils.GetCBChecked('_cbMPFilterOutsideArea');
let filter_Solved = uroUtils.GetCBChecked('_cbMPFilterSolved');
let filter_Unidentified = uroUtils.GetCBChecked('_cbMPFilterUnidentified');
let filter_Closed = uroUtils.GetCBChecked('_cbMPFilterClosed');
let filter_NotClosedUserID = uroUtils.GetCBChecked('_cbMPNotClosedUserIDFilter');
let filter_ClosedUserID = uroUtils.GetCBChecked('_cbMPClosedUserIDFilter');
let filter_Reopened = uroUtils.GetCBChecked('_cbMPFilterReopenedProblem');
let filter_LowSeverity = uroUtils.GetCBChecked('_cbMPFilterLowSeverity');
let filter_MediumSeverity = uroUtils.GetCBChecked('_cbMPFilterMediumSeverity');
let filter_HighSeverity = uroUtils.GetCBChecked('_cbMPFilterHighSeverity');
let filterTypes = [];
let i;
for(i=0; i<uroKnownProblemTypeIDs.length; i++)
{
if(uroUtils.GetCBChecked('_cbMPFilter_T'+uroKnownProblemTypeIDs[i])) filterTypes.push(uroKnownProblemTypeIDs[i]);
}
let filter_TypeUnknown = uroUtils.GetCBChecked('_cbMPFilterUnknownProblem');
let filter_TaggedElgin = uroUtils.GetCBChecked('_cbFilterElgin');
let filter_TaggedTrafficCast = uroUtils.GetCBChecked('_cbFilterTrafficCast');
let filter_TaggedTrafficMaster = uroUtils.GetCBChecked('_cbFilterTrafficMaster');
let filter_TaggedCaltrans = uroUtils.GetCBChecked('_cbFilterCaltrans');
let filter_TaggedTFL = uroUtils.GetCBChecked('_cbFilterTFL');
let filter_Invert = uroUtils.GetCBChecked('_cbInvertMPFilter');
let filter_StartDateEnabled = uroUtils.GetCBChecked('_cbMPFilterStartDate');
let filter_EndDateEnabled = uroUtils.GetCBChecked('_cbMPFilterEndDate');
let filter_EndDatePassed = uroUtils.GetCBChecked('_cbMPFilterEndDatePassed');
let tsD = uroUtils.GetElmValue('_inputMPFilterStartDay');
let tsM = uroUtils.GetElmValue('_inputMPFilterStartMonth');
let tsY = uroUtils.GetElmValue('_inputMPFilterStartYear');
let startDate = uroUtils.GetTS(tsD, tsM, tsY, 0, 0);
tsD = uroUtils.GetElmValue('_inputMPFilterEndDay');
tsM = uroUtils.GetElmValue('_inputMPFilterEndMonth');
tsY = uroUtils.GetElmValue('_inputMPFilterEndYear');
let endDate = uroUtils.GetTS(tsD, tsM, tsY, 0, 0);
let nowTime = (new Date()).getTime();
let mpObjs = sdk.DataModel.MapProblems.getAll();
for (let urobj in mpObjs)
{
let problem = mpObjs[urobj];
if(problem.origGeo === undefined)
{
// Store a copy of the original marker position if we haven't already done so
problem.origGeo = uroUtils.CloneObject(problem.geometry);
}
else
{
// Restore the original position if we do have a copy of it, to undo any adjustments
// that may have been made in the last filtering pass
problem.geometry = uroUtils.CloneObject(problem.origGeo);
}
let ureqID = problem.id;
let problemStyle = 'visible';
// check problem against current session ignore list...
if(uroIgnore.IsOnList(ureqID)) problemStyle = 'hidden';
if(uFP_masterEnable === true)
{
if(filter_OutsideEditableArea === true)
{
if(problem.isEditable === false)
{
problemStyle = 'hidden';
}
}
/*
//// https://issuetracker.google.com/issues/478409089
if(filter_EndDatePassed == true)
{
if(problem.attributes.endTime > nowTime)
{
problemStyle = 'hidden';
}
}
if(filter_StartDateEnabled == true)
{
let tStart = new Date(problem.attributes.startTime);
tStart.setHours(0);
tStart.setMinutes(0);
tStart.setSeconds(0);
tStart = tStart.getTime();
if(tStart != startDate)
{
problemStyle = 'hidden';
}
}
if(filter_EndDateEnabled == true)
{
let tEnd = new Date(problem.attributes.endTime);
tEnd.setHours(0);
tEnd.setMinutes(0);
tEnd.setSeconds(0);
tEnd = tEnd.getTime();
if(tEnd != endDate)
{
problemStyle = 'hidden';
}
}
*/
// check against closed/not identified filtering if enabled...
let domElm = sdk.Map.getFeatureDomElement({featureId: problem.id, layerName: "mapProblems"})
if(domElm !== null)
{
let problem_marker_img = domElm.href.baseVal;
if(filter_Solved === true)
{
if(problem_marker_img.indexOf('_solved') != -1) problemStyle = 'hidden';
}
if(filter_Unidentified === true)
{
if(problem_marker_img.indexOf('_rejected') != -1) problemStyle = 'hidden';
}
}
if(filter_Closed === true)
{
if(problem.isOpen === false)
{
problemStyle = 'hidden';
}
}
/*
//// https://issuetracker.google.com/issues/478409089
if(problemStyle == 'visible')
{
if(solverUser !== null)
{
if((filter_NotClosedUserID === true) && (problem.attributes.resolvedBy == solverUser)) problemStyle = 'hidden';
if((filter_ClosedUserID === true) && (problem.attributes.resolvedBy != solverUser)) problemStyle = 'hidden';
}
}
*/
if(problemStyle == 'visible')
{
let problemType = null; ////problem.attributes.subType; https://issuetracker.google.com/issues/478409089
let desc = '';
/*
https://issuetracker.google.com/issues/478409089
if(problem.attributes.description != null)
{
desc = problem.attributes.description;
}
*/
let customType = uroGetCustomType(ureqID, uroLayersFT.ID.MP, desc);
if(customType === 100)
{
if(filter_TaggedElgin === true) problemStyle = 'hidden';
}
else if(customType === 101)
{
if(filter_TaggedTrafficCast === true) problemStyle = 'hidden';
}
else if(customType === 102)
{
if(filter_TaggedTrafficMaster === true) problemStyle = 'hidden';
}
else if(customType === 103)
{
if(filter_TaggedCaltrans === true) problemStyle = 'hidden';
}
else if(customType === 104)
{
if(filter_TaggedTFL === true) problemStyle = 'hidden';
}
else if(uroKnownProblemTypeIDs.indexOf(problemType) !== -1)
{
if(filterTypes.indexOf(problemType) !== -1)
{
problemStyle = 'hidden';
}
}
else if(filter_TypeUnknown === true) problemStyle = 'hidden';
if(filter_Reopened === true)
{
if((problem.isOpen === true) && (problem.resolvedOn !== null))
{
problemStyle = 'hidden';
}
}
if(filter_Invert === true)
{
if(problemStyle == 'hidden') problemStyle = 'visible';
else problemStyle = 'hidden';
}
if(problem.severity == "low")
{
if(filter_LowSeverity === true) problemStyle = 'hidden';
}
else if(problem.severity == "medium")
{
if(filter_MediumSeverity === true) problemStyle = 'hidden';
}
else if(filter_HighSeverity === true) problemStyle = 'hidden';
}
}
let marker = uroGetMarker(uroLayersFT.ID.MP, urobj);
if(marker !== null)
{
marker.style.visibility = problemStyle;
if(problemStyle === 'hidden')
{
// To ensure WME displays the details for the topmost marker that's
// left visible in a stack, alter the coords for any hidden markers
// to place them in a location that isn't likely to be clicked on...
problem.geometry.coordinates[0] = 0;
problem.geometry.coordinates[1] = 90;
}
}
}
uroDBG.PerfMon(pmFunction, pmTStart);
}
function uroFilterPreamble()
{
let mapviewport = document.getElementsByClassName("olMapViewport")[0];
if(mapviewport === null)
{
if(uroNullMapViewport === false)
{
uroDBG.AddLog('caught null mapviewport');
uroNullMapViewport = true;
}
return false;
}
let uiElms = uroTabs.CtrlTabs[uroTabs.IDS.MISC][uroTabs.FIELDS.TABBODY];
if(uiElms == null)
{
uroDBG.AddLog('caught missing UI');
return false;
}
if(uiElms.innerHTML.length === 0)
{
uroDBG.AddLog('caught empty UI');
return false;
}
if(uroSettingsApplied === false)
{
return false;
}
uroNullMapViewport = false;
return true;
}
function uroFilterItems_MasterEnableClick()
{
if(uroUtils.GetCBChecked('_cbMasterEnable') === false)
{
uroPopup.Hide();
}
uroFilterItems();
}
function uroFilterItems()
{
uroFilterProblems();
uroFilterPlaces();
uroFilterURs();
uroRTCs.Filter();
uroFilterRAs();
uroFilterMapComments();
}
function uroFilterItemsOnMove()
{
W.map.events.unregister('mousemove',null,uroFilterItemsOnMove);
uroFilterItems();
}
function uroCheckCommentsForTag(idSrc)
{
let sdObj = uroURSessions.GetSessionData(idSrc);
if(sdObj === null) return -1;
if(sdObj.comments.length === 0) return -1;
for(let idx = sdObj.comments.length-1; idx >= 0; idx--)
{
for(let tag = 0; tag < uroCustomURTags.length; tag++)
{
let keyword = uroCustomURTags[tag];
if(sdObj.comments[idx].text.indexOf(keyword) != -1)
{
return tag;
}
}
}
return -1;
}
function uroGetCustomType(idSrc, markerType, desc)
{
let provider = '';
if(desc === null) desc = '';
if(markerType == uroLayersFT.ID.UR)
{
let ureq = W.model.mapUpdateRequests.objects[idSrc];
// early test for native speed limit URs
if(ureq.attributes.type == 23) return 98;
}
else if(markerType == uroLayersFT.ID.MP)
{
let mp = W.model.mapProblems.objects[idSrc];
if(mp.attributes.provider != null)
{
provider = mp.attributes.provider;
}
}
if(desc !== '')
{
if((markerType == uroLayersFT.ID.UR) || (markerType == 'mc'))
{
for(let tag=0; tag<uroCustomURTags.length; tag++)
{
let keyword = uroCustomURTags[tag];
if(desc.indexOf(keyword) != -1)
{
return tag;
}
}
}
if(markerType == uroLayersFT.ID.MP)
{
if(desc.indexOf('[Elgin]') != -1) return 100;
if(desc.indexOf('[ELGIN]') != -1) return 100;
if(desc.indexOf('[elginroadworks]') != -1) return 100;
if(desc.indexOf('[one.network]') != -1) return 100;
if(desc.indexOf('[TrafficCast]') != -1) return 101;
if(desc.indexOf('[TM]') != -1) return 102;
if(desc.indexOf('[Caltrans]') != -1) return 103;
if(desc.indexOf('[TfL Open Data]') != -1) return 104;
if(provider.indexOf('London TFL Closures') != -1) return 104;
}
}
if(markerType == uroLayersFT.ID.UR)
{
return uroCheckCommentsForTag(idSrc);
}
return -1;
}
function uroGetRestrictionLanes(disposition)
{
let retval = '';
if(disposition == 1) retval += 'All lanes';
else if(disposition == 2) retval += 'Left lane';
else if(disposition == 3) retval += 'Middle lane';
else if(disposition == 4) retval += 'Right lane';
else retval += ' - ';
return retval;
}
function uroGetRestrictionLaneType(laneType)
{
let retval = '';
if(laneType === null) retval += ' - ';
else
{
if(laneType == 1) retval += 'HOV';
else if(laneType == 2) retval += 'HOT';
else if(laneType == 3) retval += 'Express';
else if(laneType == 4) retval += 'Bus lane';
else if(laneType == 5) retval += 'Fast lane';
else retval += ' - ';
}
return retval;
}
let uroVehicleTypes =
[
[1280, 'fa-car'],
[1024, 'fa-motorcycle'],
[272, 'fa-taxi'],
[1808, 'fa-bolt']
];
function uroGetRestrictionVehicleTypes(restObj, allowInit, profileKey)
{
let i;
let j;
let k;
let tVT;
let retval = [];
for(i = 0; i < uroVehicleTypes.length; ++i)
{
retval.push(allowInit);
}
let tRest = restObj._driveProfiles.get(profileKey);
if(tRest !== undefined)
{
for(i = 0; i < tRest._driveProfiles.length; ++i)
{
tVT = tRest._driveProfiles[i].getVehicleTypes();
{
if(tVT.length > 0)
{
for(j = 0; j < tVT.length; ++j)
{
for(k = 0; k < uroVehicleTypes.length; ++k)
{
if(tVT[j] == uroVehicleTypes[k][0])
{
retval[k] = !allowInit;
}
}
}
}
}
}
}
return retval;
}
function uroFormatRestriction(restObj, borderStyle)
{
let retval = '';
if(restObj._defaultType == "DIFFICULT")
{
retval = '<tr><td colspan=13 ' + borderStyle + '>Difficult Turn';
}
else
{
let roDays = null;
let roFromDate = null;
let roToDate = null;
let roFromTime = null;
let roToTime = null;
let roRepeats = false;
let roAllDay = false;
if(restObj._days !== undefined)
{
roDays = restObj._days;
roFromDate = restObj._fromDate;
roToDate = restObj._toDate;
roFromTime = restObj._fromTime;
roToTime = restObj._toTime;
}
else if(restObj._timeFrames.length > 0)
{
if(restObj._timeFrames[0]._weekdays !== undefined)
{
roDays = restObj._timeFrames[0]._weekdays;
roFromDate = restObj._timeFrames[0]._startDate;
roToDate = restObj._timeFrames[0]._endDate;
roFromTime = restObj._timeFrames[0]._fromTime;
roToTime = restObj._timeFrames[0]._toTime;
roRepeats = restObj._timeFrames[0]._repeatYearly;
}
}
if((roFromTime === null) && (roToTime === null))
{
roFromTime = "0:00";
roToTime = "23:59";
roAllDay = true;
}
let hasExpired = false;
let isFuture = false;
let tNow = Date.now();
let tFrom = null;
let tTo = null;
if(roFromDate !== null)
{
tFrom = new Date(roFromDate + " " + roFromTime);
isFuture = (tFrom.getTime() > tNow);
}
if(roToDate !== null)
{
tTo = new Date(roToDate + " " + roToTime);
hasExpired = (tTo.getTime() < tNow);
}
if((hasExpired === true) && (roRepeats === true))
{
while(tTo.getTime() < tNow)
{
tFrom.setFullYear(tFrom.getFullYear() + 1);
tTo.setFullYear(tTo.getFullYear() + 1);
}
isFuture = (tFrom.getTime() > tNow);
hasExpired = false;
}
if(isFuture === true)
{
retval = '<tr bgcolor="#8080FF">';
}
else if(hasExpired === true)
{
retval = '<tr bgcolor="#FFFFC0">';
}
else
{
retval = '<tr>';
}
if(roDays === null)
{
roDays = 127;
}
let tdTag = '<td style="text-align:center;' + borderStyle + '">';
retval += tdTag;
if((roDays & 1) == 1) retval += 'M';
else retval += '-';
retval += '</td>' + tdTag;
if((roDays & 2) == 2) retval += 'Tu';
else retval += '-';
retval += '</td>' + tdTag;
if((roDays & 4) == 4) retval += 'W';
else retval += '-';
retval += '</td>' + tdTag;
if((roDays & 8) == 8) retval += 'Th';
else retval += '-';
retval += '</td>' + tdTag;
if((roDays & 16) == 16) retval += 'F';
else retval += '-';
retval += '</td>' + tdTag;
if((roDays & 32) == 32) retval += 'Sa';
else retval += '-';
retval += '</td>' + tdTag;
if((roDays & 64) == 64) retval += 'Su';
else retval += '-';
tdTag = '<td nowrap style="text-align:center;' + borderStyle + '">';
retval += '</td>' + tdTag;
if(roFromDate === null) retval += 'All dates';
else retval += tFrom.toISOString().slice(0,10) + ' to ' + tTo.toISOString().slice(0,10);
if(roRepeats === true)
{
retval += ' <i class="fa fa-repeat"> </i>';
}
retval += '</td>' + tdTag;
if((restObj._allDay === true) || (roAllDay === true)) retval += 'All day';
else retval += roFromTime + ' to ' + roToTime;
retval += '</td>' + tdTag;
retval += uroGetRestrictionLanes(restObj._disposition);
retval += '</td>' + tdTag;
retval += uroGetRestrictionLaneType(restObj._laneType);
retval += '</td>' + tdTag;
// for brevity, the popup only displays the allowed/prohibited restriction for the driveable vehicle types in the app...
let typesAllowed = [];
if((restObj._defaultType == "BLOCKED") || (restObj._defaultType == "TOLL"))
{
if(restObj._defaultType == "TOLL")
{
retval += I18n.lookup('restrictions.editing.segment.toll_road');
}
typesAllowed = uroGetRestrictionVehicleTypes(restObj, false, "FREE");
}
else
{
typesAllowed = uroGetRestrictionVehicleTypes(restObj, true, "BLOCKED");
}
let i;
for(i = 0; i < uroVehicleTypes.length; ++i)
{
if(typesAllowed[i] === true)
{
retval += '<i class="fa '+uroVehicleTypes[i][1]+'" style="color:#000000;"> </i> ';
}
else
{
retval += '<i class="fa '+uroVehicleTypes[i][1]+'" style="color:#d0d0d0;"> </i> ';
}
}
retval += '</td><td style="' + borderStyle + '">';
retval += uroUtils.Clickify(restObj._description, '');
}
retval += '</td></tr>';
return retval;
}
function uroOpenURDialog(urID)
{
let t = {showNext: false, nextButtonString: I18n.lookup('problems.panel.done')};
let urObj = W.model.mapUpdateRequests.objects[urID];
W.reqres.request("problems:browse", _.extend(t, {problem: urObj}));
}
function uroDoesLayerHaveMF(markerType)
{
let mtList = uroGetMarkerTypeList(markerType);
let mfAvailable = false;
while(mtList.length > 0)
{
let mt = mtList.pop();
if(uroLayers.layers[mt].mf !== null)
{
mfAvailable = true;
break;
}
}
return mfAvailable;
}
function uroRestackMarkers()
{
if(uroStackList.length === 0) return;
if(uroDoesLayerHaveMF(uroStackType) === true)
{
uroDBG.AddLog('restacking markers...');
// strip off the .realX/realY attributes from any UR object we've previously added it to, to allow
// the native recentering to work again...
let idList = uroGetMarkerIDs(uroStackType);
for(let marker of idList)
{
let testMarkerAttributes = uroGetAttributes(uroStackType, marker);
if(testMarkerAttributes.geometry.realX != null)
{
testMarkerAttributes.geometry.x = testMarkerAttributes.geometry.realX;
testMarkerAttributes.geometry.y = testMarkerAttributes.geometry.realY;
delete(testMarkerAttributes.geometry.realX);
delete(testMarkerAttributes.geometry.realY);
}
}
// now restack any markers that were repositioned...
for(let idx=0; idx<uroStackList.length; idx++)
{
let urM = uroGetMarker(uroStackType, uroStackList[idx]);
if(urM != null)
{
urM.style.transform = "";
}
}
uroStackList = [];
uroUnstackedMasterID = null;
uroStackType = null;
uroDBG.AddLog('...stacked!');
}
}
function uroIsIDAlreadyUnstacked(idSrc)
{
if(uroStackList.length === 0) return false;
for(let idx=0; idx<uroStackList.length; idx++)
{
if(uroStackList[idx] == idSrc) return true;
}
return false;
}
function uroHideOtherMarkers(stackType)
{
const stackTypes = [uroLayersFT.ID.VPUR, uroLayersFT.ID.UR, uroLayersFT.ID.MP];
for(let st of stackTypes)
{
let listIDs = uroGetMarkerIDs(st);
for(let mID of listIDs)
{
let toHideMarker = uroGetMarker(st, mID);
if(toHideMarker !== null)
{
if((uroIsIDAlreadyUnstacked(mID) === false) || (st !== stackType))
{
if(st === uroLayersFT.ID.VPUR)
{
uroFilterPURMarker(st, mID, 'hidden');
}
else
{
toHideMarker.style.visibility = 'hidden';
}
}
}
}
}
}
function uroCheckStacking(stackType, masterID)
{
if(typeof(masterID) === 'number')
{
masterID = masterID.toString();
}
if(uroIsIDAlreadyUnstacked(masterID) === true) return;
if(uroStackType !== null) return;
// Do the uroGetMarkerIDs call here, as this refreshes the .mf entries in uroLayers,
// which is needed for uroGetMarker to be able to find the marker if the layer markers
// have been updated by WME...
let idList = uroGetMarkerIDs(stackType);
let masterObj = uroGetMarker(stackType, masterID);
if(masterObj === null) return;
uroDBG.AddLog('checking for marker stack, masterID: '+masterID+', stackType: '+stackType);
let stackList = [];
stackList.push(masterID);
let threshSquared = uroUtils.GetElmValue('_inputUnstackSensitivity');
threshSquared *= threshSquared;
let bcr = masterObj.getBoundingClientRect();
let unstackedX = (bcr.left + bcr.right) / 2;
let unstackedY = (bcr.top + bcr.bottom) / 2;
let offset = 0.000000001;
if(uroDoesLayerHaveMF(stackType) === true)
{
let showOpen = true;
let showClosed = false;
let showTypes = null;
if(stackType === uroLayersFT.ID.UR)
{
showTypes = W?.issueTrackerController?.app?.attributes?.issueTrackerFilter?.attributes?.mapUpdateRequestsFilter?.attributes?.status;
}
else if(stackType === uroLayersFT.ID.MP)
{
showTypes = W?.issueTrackerController?.app?.attributes?.issueTrackerFilter?.attributes?.mapProblemsFilter?.attributes?.status;
}
if(showTypes !== null)
{
showOpen = ((showTypes == 'OPEN') || (showTypes == 'BOTH'));
showClosed = ((showTypes == 'CLOSED') || (showTypes == 'BOTH'));
}
for(let marker of idList)
{
let testMarkerObj = uroGetMarker(stackType, marker);
let testMarkerAttributes = uroGetAttributes(stackType, marker);
if((testMarkerAttributes !== null) && (testMarkerObj !== null))
{
// if multiple markers are stacked exactly on top of one another, WME will always open up the one which it would have rendered on the
// top of the stack in the absence of any URO+ filtering, regardless of which UR pin actually receives the click event. To prevent
// this, we give each pin in the stack a unique set of false coordinates, storing the original coordinates in newly created
// properties so they can be restored later on.
//
// As of 3.169, the offset added to create the false coordinates has now been changed to a fractional value as opposed to the large integer
// it previously was, as the latter method has now been seen to cause problems with displaying the "A" marker when a road closure request MP
// is being viewed. By using a small fractional offset added to each stacked marker, the risk of accidentally setting the offset coords to
// those of another marker is low
if(testMarkerAttributes.geometry.realX === undefined)
{
testMarkerAttributes.geometry.realX = testMarkerAttributes.geometry.x;
testMarkerAttributes.geometry.x += offset;
testMarkerAttributes.geometry.realY = testMarkerAttributes.geometry.y;
testMarkerAttributes.geometry.y += offset;
offset += 0.000000001;
}
let includeInStack = (testMarkerObj.style.visibility != 'hidden');
let isClosed = (testMarkerAttributes.open === false);
includeInStack = includeInStack && ((isClosed && showClosed) || (!isClosed && showOpen));
if(includeInStack)
{
let tmID = testMarkerAttributes.id;
if(typeof(tmID) === 'number')
{
tmID = tmID.toString();
}
if(tmID != masterID)
{
let bcr = testMarkerObj.getBoundingClientRect();
let tmx = (bcr.left + bcr.right) / 2;
let tmy = (bcr.top + bcr.bottom) / 2;
let xdiff = unstackedX - tmx;
let ydiff = unstackedY - tmy;
let distSquared = ((xdiff * xdiff) + (ydiff * ydiff));
if(distSquared < threshSquared)
{
stackList.push(tmID);
}
}
}
}
}
}
let inhibitUnstacking = (sdk.Map.getZoomLevel() < uroUtils.GetElmValue('_inputUnstackZoomLevel'));
inhibitUnstacking = inhibitUnstacking || (stackList.length < 2);
if(inhibitUnstacking == false)
{
uroStackType = stackType;
if(uroUnstackedMasterID != masterID)
{
uroDBG.AddLog('unstacked ID mismatch, relocating markers...');
uroRestackMarkers();
uroUnstackedMasterID = masterID;
uroStackList = [];
// push the highlighted marker onto the stacklist so uroIsIDAlreadyUnstacked() will return true
uroStackList.push(masterID);
let offsetX = 0;
let offsetY = 0;
for(let shoveIdx=0; shoveIdx < stackList.length; shoveIdx++)
{
let fid = stackList[shoveIdx];
let stackMarker = uroGetMarker(stackType, fid);
if(stackMarker !== null)
{
let bcr = stackMarker.getBoundingClientRect();
let smx = (bcr.left + bcr.right) / 2;
let smy = (bcr.top + bcr.bottom) / 2;
let newX = unstackedX - smx + offsetX;
let newY = unstackedY - smy - offsetY;
uroStackList.push(fid);
stackMarker.style.transform = "translate("+newX+"px, "+newY+"px)";
offsetX += 10;
offsetY += 25;
}
}
// hide other markers to prevent confusion with the unstacked markers
uroHideOtherMarkers(stackType);
// also hide the comment counts we add to URs, both so that they don't hang around
// after we've hidden the respective UR markers not in the current stack, and also
// because even for the ones that are in the stack, the counts would get in the
// way unless we repositioned them, which seems like extra effort that isn't really
// worthwhile given that once a user is into the realms of unstacking a UR cluster,
// they've probably already gleaned all the info they need from the counts and can
// do without them whilst handling the stack...
uroURExtras.RemoveCommentCounts();
}
}
else
{
uroRestackMarkers();
}
}
function uroGetCentroidish(geo)
{
let retval = null;
if(geo.type === "Point")
{
retval = geo.coordinates;
}
else if(geo.coordinates.length > 0)
{
let clon = 0;
let clat = 0;
let n = 0;
for(let i = 0; i < geo.coordinates.length; ++i)
{
for(let j = 0; j < geo.coordinates[i].length; ++j)
{
clon += geo.coordinates[i][j][0];
clat += geo.coordinates[i][j][1];
++n;
}
}
retval = [(clon / n), (clat / n)];
}
return retval
}
function uroGetVenueNavPoint(uroFID)
{
let retval = W.map.getUnprojectedCenter(); // allow the function to return a safe value in case we can't find the requested venue object...
let vObj = sdk.DataModel.Venues.getById({venueId: uroFID});
if(vObj !== undefined)
{
if(vObj.navigationPoints.length > 0)
{
// if the venue has any navpoints defined, use the position of the first one
let tPoint = vObj.navigationPoints[0].point;
retval.lon = tPoint.coordinates[0];
retval.lat = tPoint.coordinates[1];
}
else
{
// otherwise use the centrepoint of the venue point or polygon
let tPoint = uroGetCentroidish(vObj.geometry);
if(tPoint !== null)
{
retval.lon = tPoint[0];
retval.lat = tPoint[1];
}
}
}
return retval;
}
function uroOpenNewTab()
{
// flush the current settings into localStorage before the new tab opens, so that when its instance of
// URO+ fires up it'll have the same settings as this one
uroConfig.SaveSettings();
return true;
}
function uroGetFeatureRenderIntent(moObj)
{
let retval = "unknown";
if(moObj !== null)
{
let isSelected = moObj.selected;
if(isSelected === true)
{
retval = "highlightselected";
}
else
{
retval = "highlight";
}
}
return retval;
}
function uroExclusiveCB()
{
let cbChecked = uroUtils.GetCBChecked(this.id);
if(cbChecked === true)
{
let pairedList = this.attributes.pairedWith.value.split(',');
for(let i=0; i<pairedList.length; i++)
{
uroUtils.SetCBChecked(pairedList[i], false);
}
}
}
function uroContainsPoint(geo, point)
{
let retval = false;
try
{
let j = 1;
for(let i = 0; i < geo.length; ++i)
{
if
(
((point[1] >= geo[i][1]) && (point[1] <= geo[j][1])) ||
((point[1] >= geo[j][1]) && (point[1] <= geo[i][1]))
)
{
let lx = geo[i][0];
if(geo[i][1] != geo[j][1])
{
let g = ((point[1] - geo[i][1]) / (geo[j][1] - geo[i][1]));
lx += (g * (geo[j][0] - geo[i][0]));
}
if(point[0] <= lx)
{
retval = !retval;
}
}
if(++j == geo.length)
{
j = 0;
}
}
}
catch
{
}
return retval;
}
function uroGetAMs(e)
{
if(uroMTEMode) return;
if(!uroFilterPreamble) return;
if(!uroInit.initialised) return;
if(document.getElementById("uroAMList") == null) return;
if(document.getElementsByClassName('topbar') == null) return;
if(uroUtils.GetCBChecked("_cbMoveAMList") === false)
{
document.getElementsByClassName('area-managers-region')[0].style.display = "block";
uroAMList.innerHTML = uroUtils.ModifyHTML("");
document.getElementsByClassName('topbar')[0].style.backgroundColor=null;
return;
}
let mapBCR = document.getElementById('map').getBoundingClientRect();
let mouseX = e.pageX - mapBCR.left;
let mouseY = e.pageY - mapBCR.top;
if((isNaN(mouseX) == true) || (isNaN(mouseY) == true))
{
return;
}
document.getElementsByClassName('topbar')[0].style.backgroundColor="#000000";
document.getElementsByClassName('topbar')[0].style.height="auto";
document.getElementsByClassName('area-managers-region')[0].style.display = "none";
let amList = '';
let tName = '';
if(sdk.Map.isLayerVisible({layerName: 'area_managers'}) === true)
{
let mousePixel = sdk.Map.getLonLatFromPixel({x: mouseX, y: mouseY});
let mousePoint = [];
mousePoint.push(mousePixel.lon);
mousePoint.push(mousePixel.lat);
let hideL1Areas = uroUtils.GetCBChecked("_cbHideAMA-L1");
let hideL2Areas = uroUtils.GetCBChecked("_cbHideAMA-L2");
let hideL3Areas = uroUtils.GetCBChecked("_cbHideAMA-L3");
let hideL4Areas = uroUtils.GetCBChecked("_cbHideAMA-L4");
let hideL5Areas = uroUtils.GetCBChecked("_cbHideAMA-L5");
let hideL6Areas = uroUtils.GetCBChecked("_cbHideAMA-L6");
let hideL7Areas = uroUtils.GetCBChecked("_cbHideAMA-L7");
let hideNewAreas = uroUtils.GetCBChecked("_cbEnableAMAMinAgeFilter");
let areaMinAge = uroUtils.GetElmValue("_inputFilterAMA-MinDays");
let hideOldAreas = uroUtils.GetCBChecked("_cbEnableAMAMaxAgeFilter");
let areaMaxAge = uroUtils.GetElmValue("_inputFilterAMA-MaxDays");
let managedAreas = sdk.DataModel.ManagedAreas.getAll();
for(let amObj in managedAreas)
{
let ama = managedAreas[amObj];
let userName = ama.userName;
let userLevel = uroUtils.GetUserLevelFromName(userName); //// https://issuetracker.google.com/issues/473067424
let hideArea = false;
hideArea = hideArea || ((hideL1Areas === true) && (userLevel === 1));
hideArea = hideArea || ((hideL2Areas === true) && (userLevel === 2));
hideArea = hideArea || ((hideL3Areas === true) && (userLevel === 3));
hideArea = hideArea || ((hideL4Areas === true) && (userLevel === 4));
hideArea = hideArea || ((hideL5Areas === true) && (userLevel === 5));
hideArea = hideArea || ((hideL6Areas === true) && (userLevel === 6));
hideArea = hideArea || ((hideL7Areas === true) && (userLevel === 7));
/*
//// https://issuetracker.google.com/issues/484308800
if((hideNewAreas === true) || (hideOldAreas === true))
{
let amaDaysAgo = uroUtils.DateToDays(ama.attributes.createdOn);
hideArea = hideArea || ((hideNewAreas === true) && (amaDaysAgo < areaMinAge));
hideArea = hideArea || ((hideOldAreas === true) && (amaDaysAgo > areaMaxAge));
}
*/
if(hideArea === false)
{
let nc = ama.geometry.coordinates.length;
for(let i = 0; i < nc; ++i)
{
let geo = ama.geometry.coordinates[i];
if(uroContainsPoint(geo, mousePoint) === true)
{
if(amList.indexOf(userName) === -1)
{
if(amList !== '') amList += ', ';
tName = uroUtils.GetUserNameAndRank(userName);
if(tName.indexOf('a href') !== -1)
{
tName = tName.replace('a href', 'a style="color:#c0c0ff;" href');
}
amList += tName;
break;
}
}
}
}
}
if(amList === '')
{
amList = 'none';
}
amList = " - <b>Area Managers:</b> "+amList;
}
document.getElementById("uroAMList").innerHTML = uroUtils.ModifyHTML(amList);
}
function uroNewTabAtMouseLoc(x, y)
{
let mPos = uroUtils.ConvertMercatorToWGS84(sdk.Map.getLonLatFromPixel({x: x, y: y}));
let nZoom = sdk.Map.getZoomLevel();
if(nZoom < 17) nZoom = 17;
let nHref = window.location.origin + window.location.pathname;
nHref += '?lon=' + mPos.lon;
nHref += '&lat=' + mPos.lat;
nHref += '&zoomLevel=' + nZoom;
window.open(nHref);
}
function uroMouseDown(e)
{
uroMouseIsDown = true;
if((e.altKey === true) && (e.ctrlKey === true))
{
uroNewTabAtMouseLoc(e.offsetX, e.offsetY);
}
}
function uroMouseUp()
{
uroMouseIsDown = false;
}
function uroTestPointerOutsideMap(mX, mY)
{
let mapElm = document.getElementById("map");
if(mapElm === undefined) return;
let mapBCR = mapElm.getBoundingClientRect();
if
(
(mX < mapBCR.left) ||
(mX > mapBCR.right) ||
(mY < mapBCR.top) ||
(mY > mapBCR.bottom)
)
{
if(uroUtils.GetCBChecked('_cbKillInertialPanning') === true)
{
let controller = null;
if (W.map.navigationControl)
{
controller = W.map.navigationControl;
}
else if(W.map.controls.find(control => control.CLASS_NAME == 'OpenLayers.Control.Navigation'))
{
controller = W.map.controls.find(control => control.CLASS_NAME == 'OpenLayers.Control.Navigation');
}
if (controller !== null)
{
controller.dragPan.panMapStart();
}
}
return true;
}
else
{
return false;
}
}
function uroMouseOut(e)
{
if(uroTestPointerOutsideMap(e.clientX, e.clientY))
{
uroPopup.Hide();
}
}
function uroUREvent_onObjectsAdded()
{
if(uroUtils.GetCBChecked('_cbURResolverIDFilter') === true)
{
uroUpdateEditorList(W.model.mapUpdateRequests.objects, '_selectURResolverID', false, false, true, false);
}
}
function uroGetSelectedURCommentCount()
{
let sdObj = uroURSessions.GetSessionData(uroSelectedURID);
if(sdObj !== null)
{
let cachedCommentCount = sdObj.comments.length;
uroDBG.AddLog(uroSelectedURID+':'+cachedCommentCount+' '+uroExpectedCommentCount);
// if there aren't the same number of cached comments as there are comments in the UR dialog list, initiate
// a refresh of the comment data...
if(cachedCommentCount != uroExpectedCommentCount)
{
if(uroPendingCommentDataRefresh === true)
{
if(cachedCommentCount > 0)
{
uroCachedLastCommentID = sdObj.comments[cachedCommentCount-1].id;
}
else
{
uroCachedLastCommentID = null;
}
uroDBG.AddLog('session data refresh required for UR '+uroSelectedURID);
if(uroCachedLastCommentID !== null)
{
uroDBG.AddLog('last comment ID for this UR is '+uroCachedLastCommentID);
}
else
{
uroDBG.AddLog('first comment for this UR, no previous comment to ID');
}
sdk.DataModel.updateRequestSessions.getUpdateRequestDetails({mapUpdateRequestId: parseInt(uroSelectedURID)});
// the refresh process has started so we're no longer pending, but we are now waiting for the request to return...
uroPendingCommentDataRefresh = false;
uroWaitingCommentDataRefresh = true;
}
else
{
if(cachedCommentCount > 0)
{
let currentLastCommentID = sdObj.comments[cachedCommentCount-1].id;
if(currentLastCommentID == uroCachedLastCommentID)
{
// most recent comment loaded for this UR is the same one that was present at the start of this
// refresh process, so kick back into pending mode so we can retry the .get()...
uroDBG.AddLog('latest comment ID still the same, reverting to pending mode...');
uroPendingCommentDataRefresh = true;
}
else
{
// something may have gone awry here - the most recent comment loaded for this UR doesn't have the
// same ID as the one present at the start of the refresh process, yet the comment counts still don't
// match up, which suggests either a comment got lost along the way or someone else has commented on
// the same UR at almost the same time. To get out of the loop this would create, assume that a
// mismatch in the IDs means the .get() has completed successfully no matter what the new comment
// count is, and take this new count to be the count we were expecting all along...
uroDBG.AddLog('latest comment ID different, but expected count not correct...');
uroExpectedCommentCount = cachedCommentCount;
}
}
else
{
uroDBG.AddLog('first comment on this UR not received yet, reverting to pending mode...');
uroPendingCommentDataRefresh = true;
}
}
}
else
{
// if the WME session is loaded with a UR already selected, such that WME has opened the UR dialog as part
// of the session startup process, adding new comments to the UR cause the cached data to be updated immediately.
// This prevents URO+ from switching into waiting mode in the above block of code, so we have to instead do
// it here by comparing the cached count against the expected count following the Send click event.
if(cachedCommentCount >= uroExpectedCommentCount)
{
uroPendingCommentDataRefresh = false;
uroWaitingCommentDataRefresh = true;
uroExpectedCommentCount = null;
}
// once the cached data has been updated, refilter the URs so that the new comment count is taken into account
// immediately for filtering and display purposes
if(uroWaitingCommentDataRefresh === true)
{
uroWaitingCommentDataRefresh = false;
uroFilterURs();
uroDBG.AddLog('refresh complete');
}
}
}
}
function uroAddedComment()
{
// when the user clicks the Send button to submit a new UR comment, this event handler fires before the new comment is
// posted to the server and thus also before the comment list gets updated in the UR dialog. So we take the current
// comment count and, if the new comment edit box isn't empty, increment it by 1 to get the expected count. Then we
// set the pending flag true to initiate a session refresh on the next 100ms tick
let sdObj = uroURSessions.GetSessionData(uroSelectedURID);
if(sdObj !== null)
{
uroExpectedCommentCount = sdObj.comments.length;
if(document.getElementsByClassName('new-comment-text')[0].value !== '')
{
uroExpectedCommentCount++;
uroDBG.AddLog('new comment added to UR '+uroSelectedURID+', cache refresh required...');
uroPendingCommentDataRefresh = true;
}
else
{
uroPendingCommentDataRefresh = false;
}
}
}
function uroInhibitNextUpdateRequestButton(e)
{
e.stopPropagation();
let doClick = true;
if(document.getElementsByClassName('form-control new-comment-text').length > 0)
{
if(document.getElementsByClassName('form-control new-comment-text')[0].textLength > 0)
{
uroAlertBox.Show("fa-warning", "URO+ Warning", "Comment not sent, close report panel anyway?", true, "Yes", "No", uroCloseReportPanel, null);
// set doClick to false here, as uroCloseReportPanel will be called by the alert box handler if required...
doClick = false;
}
}
// no alert box has been generated, so close the panel
if(doClick)
{
uroCloseReportPanel();
}
}
function uroCloseReportPanel()
{
document.getElementsByClassName('close-panel')[0].click();
}
function uroIncrementClosureDate(oldDate, incByDays)
{
// Thanks to WME no longer using consistent ISO8601 date formatting when displaying
// closure details, parsing the date string is now somewhat more involved. Thanks devs...
// Default to returning the existing date, just in case we can't increment it
let retval = oldDate;
let sepChar = null;
// Search through oldDate for a non-digit character, so we know what's
// being used to seperate the year, month and date values by this locale...
for(let i = 0; i < oldDate.length; ++i)
{
if((oldDate[i] < '0') || (oldDate[i] > '9'))
{
sepChar = oldDate[i];
break;
}
}
if(sepChar != null)
{
// First build a "probe" date object we can use to determine what the user's WME locale
// does with dates.
let incDate = new Date();
incDate.setFullYear(3000);
incDate.setMonth(10);
incDate.setDate(22);
// With these three carefully chosen date elements set, we now generate a localised datestring
// using the WME locale setting - the first character of which then tells us the date
// format - 1 = MDY (ack ptui), 2 = DMY (good), 3 = YMD (sweet!)
let localeDate = incDate.toLocaleDateString(I18n.locale);
let dateFormat = localeDate[0];
// Now we know the seperator character and the date format, so we can finally start to parse
// the existing date string...
let oldDateBits = oldDate.split(sepChar);
let datePos;
let monthPos;
let yearPos;
if(dateFormat == '1')
{
datePos = 1;
monthPos = 0;
yearPos = 2;
}
else if(dateFormat == '2')
{
datePos = 0;
monthPos = 1;
yearPos = 2;
}
else
{
datePos = 2;
monthPos = 1;
yearPos = 0;
}
incDate.setFullYear(parseInt(oldDateBits[yearPos]));
incDate.setMonth(parseInt(oldDateBits[monthPos]) - 1);
incDate.setDate(parseInt(oldDateBits[datePos]));
let tDate = new Date(incDate.getTime());
incDate.setDate(tDate.getDate() + incByDays);
retval = incDate.toLocaleDateString(I18n.locale);
// Except for those pesky locales where toLocaleDateString() doesn't *quite*
// return what WME is expecting. Because, you know, why make it easy for
// scripters when you can chuck in a few subtle curveballs like this, eh...
if(I18n.locale == 'bg')
{
// Remove the "r." suffix
retval = retval.split(" ")[0] + " ";
}
}
return retval;
}
function uroGetElementProperty(elmName, elmOffset, elmProperty)
{
let retval = null;
if(document.getElementsByName(elmName).length > elmOffset)
{
retval = document.getElementsByName(elmName)[elmOffset][elmProperty];
}
else if(document.getElementById(elmName) !== null)
{
retval = document.getElementById(elmName)[elmProperty];
}
return retval;
}
function uroGetShadowElementProperty(elmName, shadowElmType, property)
{
let retval = null;
let tObj = document.getElementById(elmName);
if(tObj !== null)
{
let sObj = tObj.shadowRoot.querySelector(shadowElmType);
if(sObj !== null)
{
retval = sObj[property];
}
}
return retval;
}
function uroTSTPopupHandler()
{
if(document.getElementsByClassName('panel')[0] === undefined)
{
uroHidePopupOnPanelOpen = true;
}
if(uroPopup.shown === true)
{
let hidePopup = false;
if(document.getElementsByClassName('panel')[0] != null)
{
if(uroHidePopupOnPanelOpen === true)
{
hidePopup = true;
uroHidePopupOnPanelOpen = false;
}
}
if(hidePopup === true)
{
uroPopup.Hide();
}
}
if((uroAFN.hoverObj !== null) && (uroAFN.hoverTime != -1) && (uroAFN.overlayShown === false))
{
if(++uroAFN.hoverTime > 5)
{
uroAFN.OverlaySetup();
}
}
uroAFN.ReplaceAreaNames(false);
if(uroPopup.autoHideTimer > 0)
{
if(--uroPopup.autoHideTimer === 0)
{
uroPopup.Hide();
}
}
if(uroPopup.timer > 0)
{
if(uroPopup.mouseIn === false)
{
uroPopup.timer--;
}
}
if(uroPopup.timer === 0)
{
uroPopup.Hide();
}
}
function uroTSTNextBtnHandler()
{
// replaces the "next xxx" button on UR, MP and PUR editing UIs
// Correctly determining what WME is displaying for the "next" button in the UR/MP/(P)PUR panel is not trivial due to
// inconsistencies in the panel behaviour depending on whether it was opened by clicking directly on the relevant
// marker, or by clicking on the associated feed entry... For PURs, there's also the added complication of multi-part
// update requests, where the same marker/panel are used to access more than one request and where, therefore, we need
// to enable access to all requests contained within the PUR, but still inhibit the "next" button once the last
// request in the multi-part sequence has been viewed.
//
// For directly-accesed markers, the "next" button caption is:
//
// URs = "Next update request" (update_requests.panel.next)
// MPs = "Next map problem" (problems.panel.next)
// PURs = "Next place" for single-part PURs or for the last part of a multi-part PUR (venues.update_requests.panel.next_venue)
// = "Next" for all but the last part of a multi-part PUR (venues.update_requests.panel.next)
// PPURs = "Next place" (venues.update_requests.panel.next_venue)
//
// For markers accessed via the feed, the "next" button caption always appears to be "Next issue" (feed.issues.next)
let reportPanel = document.querySelector('#panel-container');
if(reportPanel.childElementCount > 0)
{
let nurButton = reportPanel.getElementsByClassName('next')[0];
if(nurButton === undefined)
{
nurButton = reportPanel.getElementsByClassName('next-venue')[0];
}
if(nurButton !== undefined)
{
let doneString = I18n.lookup('problems.panel.done');
let btnCaptionIsNextPlace = (nurButton.innerHTML.indexOf(I18n.lookup('venues.update_requests.panel.next_venue')) !== -1);
let btnCaptionIsDefaultUR = (nurButton.innerHTML.indexOf(I18n.lookup('update_requests.panel.next')) !== -1);
let btnCaptionIsDefaultMP = (nurButton.innerHTML.indexOf(I18n.lookup('problems.panel.next')) !== -1);
let btnCaptionIsNextIssue = (nurButton.innerHTML.indexOf(I18n.lookup('feed.issues.next')) !== -1);
let updateButton = false;
let panelClass = reportPanel.childNodes[0].childNodes[0].className;
let isURorMPPanel = (panelClass.indexOf('problem-edit') !== -1);
let isPURPanel = (panelClass.indexOf('place-update') !== -1);
if(isURorMPPanel === true)
{
// user has enabled UR button mod?
if(uroUtils.GetCBChecked('_cbInhibitNURButton') === true)
{
// the native UR panel button will always either be "Next update request" or "Next issue"
updateButton = ((btnCaptionIsDefaultUR) || (btnCaptionIsNextIssue));
}
// user has enabled MP button mod?
if(uroUtils.GetCBChecked('_cbInhibitNMPButton') === true)
{
// there's no way to determine if the edit panel has been opened for a UR or a MP, however as MPs
// don't currently appear in the feed, the native button only uses "Next map problem" as its caption
updateButton = (updateButton || btnCaptionIsDefaultMP);
}
}
else if(isPURPanel === true)
{
if(uroUtils.GetCBChecked('_cbInhibitNPURButton') === true)
{
// for a (P)PUR, only modify the button if it's showing the "Next place" or "Next issue" caption, to
// avoid messing up the "Next" button used to move to the next part of a multi-part PUR...
updateButton = ((btnCaptionIsNextPlace === true) || (btnCaptionIsNextIssue));
}
}
if(updateButton === true)
{
uroDBG.AddLog('inhibit Next UR/MP/PUR button');
// alter the button caption
nurButton.innerHTML = uroUtils.ModifyHTML(doneString);
// Add a new click handler to override the native one - this acts both to prevent the normal action of the "Next UR/MP/PUR" button in
// moving to the next UR/MP/PUR, and also allows us to warn about closing the UR panel if there's an unsent comment...
nurButton.addEventListener("click", uroInhibitNextUpdateRequestButton, false);
}
}
uroInhibitURFiltering = false;
}
}
function uroTSTCommentAddedHandler()
{
// test for the opening or closing of the UR editing dialog so we can detect when a new comment is added
let URDialogIsOpen = false;
let panelOpen = (document.getElementById('panel-container').firstChild !== null);
if(panelOpen)
{
URDialogIsOpen = (document.getElementById('panel-container').getElementsByClassName('conversation').length > 0);
}
if(URDialogIsOpen)
{
let thisSelectedURID = document.getElementsByClassName('permalink')[0].href.split('&mapUpdateRequest=');
if(thisSelectedURID.length > 1)
{
thisSelectedURID = thisSelectedURID[1].split('&')[0];
}
else
{
thisSelectedURID = null;
}
if((thisSelectedURID != uroSelectedURID) || ((thisSelectedURID != uroMarkers.clickedOnID) && (uroMarkers.clickedOnID != null)))
{
// if the user selects a new UR whilst the editing dialog is still open, treat it in the
// same way as if the user had selected that UR with the dialog closed
uroURDialogIsOpen = false;
uroSelectedURID = null;
}
if(((uroURDialogIsOpen === false) && (uroSelectedURID === null)) || (uroURReclickAttempts > 0))
{
// user is editing a new UR
// add our own click event handler to the Send button, so we can do stuff whenever a new comment is added
if(document.getElementsByClassName('new-comment-form').length > 0)
{
if(document.getElementsByClassName('new-comment-form')[0].getElementsByClassName('send-button').length > 0)
{
document.getElementsByClassName('new-comment-form')[0].getElementsByClassName('send-button')[0].addEventListener("click", uroAddedComment, false);
uroSelectedURID = thisSelectedURID;
uroDBG.AddLog('user is editing UR '+uroSelectedURID);
let sdObj = uroURSessions.GetSessionData(uroSelectedURID);
if(sdObj !== null)
{
uroExpectedCommentCount = sdObj.comments.length;
}
if((uroHoveredURID !== null) && (uroSelectedURID !== null) && (parseInt(uroHoveredURID) !== parseInt(uroSelectedURID)))
{
if(uroURReclickAttempts === 0)
{
uroDBG.AddLog('DANGER, WILL ROBINSON! You clicked on UR ID '+uroHoveredURID+' but WME has loaded the details for UR ID '+uroSelectedURID+' instead, attempting to fix...');
}
if(++uroURReclickAttempts < 3)
{
//uroRestackMarkers();
let urMarker = uroGetMarker(uroLayersFT.ID.UR,uroHoveredURID);
if(urMarker !== null)
{
let urMarkerAttributes = uroGetAttributes(uroLayersFT.ID.UR, uroHoveredURID);
if(urMarkerAttributes !== null)
{
urMarkerAttributes.geometry.x = urMarkerAttributes.geometry.realX;
urMarkerAttributes.geometry.y = urMarkerAttributes.geometry.realY;
uroOpenURDialog(uroHoveredURID);
}
}
return;
}
else
{
uroDBG.AddLog('Woe is me, attempting to open UR ID '+uroHoveredURID+' has failed...');
uroAlertBox.Show('fa-warning', 'URO+ Warning', 'WME may have opened the details panel for a different UR to the one you selected, proceed with caution', false, "OK", "", null, null);
}
}
uroURReclickAttempts = 0;
uroFilterURs();
}
}
}
}
else if(uroURDialogIsOpen === true)
{
// dialog was open and has now been closed
uroSelectedURID = null;
uroMarkers.clickedOnID = null;
uroFilterURs();
}
uroURDialogIsOpen = URDialogIsOpen;
if(((uroPendingCommentDataRefresh === true) || (uroWaitingCommentDataRefresh === true)) && (uroSelectedURID !== null))
{
uroDBG.AddLog('check completion of comment data refresh for UR '+uroSelectedURID+' ('+uroPendingCommentDataRefresh+','+uroWaitingCommentDataRefresh+')');
uroGetSelectedURCommentCount();
}
}
function uroClosureListFiltering()
{
// List entry filtering
let entryElms = document.querySelector('.closures-list')?.querySelectorAll('wz-card');
if(entryElms !== undefined)
{
let nEntries = entryElms.length;
if(nEntries > 0)
{
let filterExpired = uroUtils.GetCBChecked('_cbHideExpiredSidepanelRTCs');
let filterCurrent = uroUtils.GetCBChecked('_cbHideSidepanelRTCs');
let filterFuture = uroUtils.GetCBChecked('_cbHideFutureSidepanelRTCs');
let filterUnknown = uroUtils.GetCBChecked('_cbHideUnknownSidepanelRTCs');
for(let i = 0; i < nEntries; ++i)
{
let hide = false;
let cElm = entryElms[i];
let ciSrc = cElm.querySelector('wz-image-chip')?.imageSrc;
if(ciSrc != undefined)
{
hide = hide || ((ciSrc.indexOf('finished') != -1) && (filterExpired == true));
hide ||= ((ciSrc.indexOf('active') != -1) && (filterCurrent == true));
hide ||= ((ciSrc.indexOf('not-started') != -1) && (filterFuture == true));
hide ||= ((ciSrc === "") && (filterUnknown == true));
}
if(hide == true)
{
cElm.parentElement.style.display = "none";
}
else
{
cElm.parentElement.style.display = "block";
}
}
}
}
}
function uroClosureListAddCloneCtrls()
{
// Closure cloning controls
if((document.querySelectorAll('.closures-list').length > 0) && (document.querySelector('.closures-list').getAttribute('touchedbyuro_cloning') === null))
{
let nClosures;
let cLoop;
let btnElm;
// Cloning doesn't work with certain locales due to the way the date strings are formatted...
if
(
(I18n.locale == "fa-IR") ||
(I18n.locale == 'ar') ||
(I18n.locale == 'zh') ||
(I18n.locale == 'ko')
)
{
// Sorry :-(
}
else
{
// for the others, are there any closures defined for all of the selected segment(s)...
if(document.getElementsByClassName('full-closures').length > 0)
{
nClosures = document.getElementsByClassName('full-closures')[0].querySelectorAll('.closure-item.is-editable').length;
if(nClosures > 0)
{
// Force a refresh of uroRTCObjs for the selected segment, as this is no longer guaranteed to have already occurred...
let selectedSegIDs = uroUtils.GetSelectedSegmentIDs();
if(selectedSegIDs.length > 0)
{
uroRTCs.GetForSelectedSegment(selectedSegIDs[0]);
// and if so, have we already added the clone icon?
for(cLoop = 0; cLoop < nClosures; cLoop++)
{
btnElm = document.getElementsByClassName('full-closures')[0].querySelectorAll('.closure-item.is-editable')[cLoop].getElementsByClassName('closure-title')[0];
if((btnElm.innerHTML.indexOf('_uroCloneClosure-') == -1) && (uroRTCs.GetOrigin(uroRTCObjs[cLoop]) !== uroEnums.TRTC.UNKNOWN))
{
let newNode = document.createElement("div");
let anchorID1 = '_uroCloneClosure-1-'+cLoop;
let anchorID2 = '_uroCloneClosure-7-'+cLoop;
let newAnchor = '<a id="'+anchorID1+'" href="#">';
newAnchor += "<i style='font-size: 150%; cursor: copy' class='fa fa-copy'></i>";
newAnchor += "</a><sup>+1</sup> ";
newAnchor += '<a id="'+anchorID2+'" href="#">';
newAnchor += "<i style='font-size: 150%; cursor: copy' class='fa fa-copy'></i>";
newAnchor += "</a><sup>+7</sup>";
newNode.innerHTML = uroUtils.ModifyHTML(newAnchor);
btnElm.prepend(newNode);
uroUtils.AddEventListener(anchorID1,"click",uroRTCClone.Clone,false);
uroUtils.AddEventListener(anchorID2,"click",uroRTCClone.Clone,false);
let chipElm = btnElm.querySelector("wz-image-chip");
chipElm.innerHTML = chipElm.innerHTML.split('>')[0] + '>';
}
}
}
}
}
}
document.querySelector('.closures-list').setAttribute('touchedbyuro_cloning','true');
}
}
function uroClosureListAddDeleteAll()
{
// Add a "Delete All" button if there's more than one closure in the list that can be deleted...
if(document.querySelector('.closures-list')?.getAttribute('touchedbyuro_deleteall') !== null)
{
return;
}
let deletableElms = uroRTCClone.GetDeletableClosures();
if(deletableElms.length < 2)
{
return;
}
if(document.getElementById('_btnDeleteAllClosures') === null)
{
let daDiv = document.createElement('wz-button');
daDiv.className = 'delete-all-button btn is-expanded'; //btn-primary';
daDiv.id = '_btnDeleteAllClosures';
let tHTML = '<i class="fa fa-trash"></i> '+I18n.lookup("closures.delete_confirm_no_reason") + ' ('+I18n.lookup("closures.apply_to_all")+')';
daDiv.innerHTML = uroUtils.ModifyHTML(tHTML);
daDiv.style.width = '100%';
daDiv.style.marginBottom = '10px';
let acBtn = document.getElementsByClassName('add-closure-button')[0];
if(acBtn !== undefined)
{
acBtn.parentNode.insertBefore(daDiv, acBtn.nextSibling);
uroUtils.AddEventListener('_btnDeleteAllClosures',"click", uroRTCClone.DeleteAll, false);
}
}
document.querySelector('.closures-list').setAttribute('touchedbyuro_deleteall','true');
}
function uroClosureListHandler()
{
// Handles adjustments to the closure list in the sidepanel...
if(uroUtils.GetCBChecked('_cbMasterEnable') === false)
{
return;
}
uroClosureListFiltering();
////uroClosureListAddCloneCtrls();
uroClosureListAddDeleteAll();
}
function uroMiscUITweaksHandler()
{
if(uroFilterPreamble())
{
// clickifies the ExtraInfo URL present in some MPs
{
if(document.getElementById('panel-container').getElementsByClassName('extraInfo').length > 0)
{
if(document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].touchedByURO === undefined)
{
let tDesc = document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].innerHTML;
tDesc = uroUtils.Clickify(tDesc, '');
document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].innerHTML = uroUtils.ModifyHTML(tDesc);
document.getElementById('panel-container').getElementsByClassName('extraInfo')[0].touchedByURO = true;
}
}
}
}
}
function uroMainTick()
{
if(uroMTEMode) return;
if(uroInit.setupListeners)
{
if(uroInit.finalisingListenerSetup === false)
{
if(sdk.State.isLoggedIn())
{
if(document.getElementsByClassName('topbar').length === 0) return;
uroInit.FinalizeListenerSetup();
document.getElementsByClassName('topbar')[0].appendChild(uroAMList);
}
}
}
else
{
if(uroUtils.GetCBChecked('_cbMasterEnable') === true)
{
// do one maintick handler call in each 10ms cycle to minimise the time stuck within the maintick handler without
// unduly affecting the overall response time for each individual handler
if(uroMainTickStage === 0) uroTSTPopupHandler();
if(uroMainTickStage == 1) uroTSTNextBtnHandler();
if(uroMainTickStage == 2) uroTSTCommentAddedHandler();
if(uroMainTickStage == 4) uroMiscUITweaksHandler();
if(++uroMainTickStage == 6) uroMainTickStage = 0;
}
}
}
function uroNewLookCheckDetailsRequest()
{
let thisurl = document.location.href;
let doRetry = true;
let urID;
let endmarkerpos = thisurl.indexOf('&endshow');
let showmarkerpos = thisurl.indexOf('&showturn=');
if((endmarkerpos != -1) && (showmarkerpos != -1))
{
showmarkerpos += 10;
uroDBG.AddLog('showturn tab opened');
urID = thisurl.substring(showmarkerpos, endmarkerpos);
uroDBG.AddLog(' turn problem ID = '+urID);
try
{
uroGetMarker(uroLayersFT.ID.MP,urID).click();
doRetry = false;
}
catch(err)
{
uroDBG.AddLog('problems not fully loaded, retrying...');
}
if(doRetry) window.setTimeout(uroNewLookCheckDetailsRequest,500);
}
}
function uroUpdateVenueEditorLists()
{
let vObjs = sdk.DataModel.Venues.getAll();
if(vObjs.length === 0) return;
// build the list of all userIDs contained in the currently loaded venue objects
let selectedIdx = null;
let listedIDs = [];
let idx;
for(idx in vObjs)
{
let obj = vObjs[idx];
let cbID = obj.modificationData.createdBy;
let ubID = obj.modificationData.updatedBy;
if((cbID !== null) && (listedIDs.indexOf(cbID) == -1))
{
listedIDs.push(cbID);
}
if((ubID !== null) && (ubID !== cbID) && (listedIDs.indexOf(ubID) == -1))
{
listedIDs.push(ubID);
}
}
// check for any previously selected userIDs in the two selector lists, then clear both lists
// and repopulate using the newly gathered ID collection from above, and finally reselect the
// previously selected user if they're still present in the new list...
let selector;
let selectedUser;
let users = W.model.users.getByIds(listedIDs);
let selectorEntry;
for(let i=0; i<2; i++)
{
if(i === 0) selector = document.getElementById('_selectPlacesUserID');
else selector = document.getElementById('_selectHidePlacesUserID');
selectedUser = null;
if(selector.selectedOptions[0] != null)
{
selectedUser = parseInt(selector.selectedOptions[0].value);
}
while(selector.options.length > 0)
{
selector.options.remove(0);
}
selector.options.add(new Option('<select a user>', null));
if(listedIDs.length > 0)
{
selectorEntry = '';
for(idx=0; idx<users.length; idx++)
{
if(users[idx].attributes.userName === undefined)
{
selectorEntry = users[idx].attributes.id;
}
else
{
selectorEntry = users[idx].attributes.userName;
}
selector.options.add(new Option(selectorEntry, users[idx].id));
if(users[idx].attributes.id == selectedUser)
{
selectedIdx = idx+1;
}
}
}
if(selectedIdx !== null)
{
selector.selectedIndex = selectedIdx;
}
}
}
function uroPlacesEditorSelected()
{
let selector = document.getElementById('_selectPlacesUserID');
if(selector.selectedIndex > 0)
{
document.getElementById('_textPlacesEditor').value = document.getElementById('_selectPlacesUserID').selectedOptions[0].innerHTML;
}
}
function uroHidePlacesEditorSelected()
{
let selector = document.getElementById('_selectHidePlacesUserID');
if(selector.selectedIndex > 0)
{
document.getElementById('_textHidePlacesEditor').value = document.getElementById('_selectHidePlacesUserID').selectedOptions[0].innerHTML;
}
}
function uroSetStyles(obj)
{
obj.style.fontSize = '12px';
obj.style.lineHeight = '100%';
obj.style.flex = '1';
obj.style.overflowY = 'auto';
}
function uroSetSectionTabStyles()
{
for(let i =0; i < uroTabs.CtrlTabs.length; ++i)
{
uroSetStyles(uroTabs.CtrlTabs[i][uroTabs.FIELDS.TABBODY]);
}
}
function uroPlacesGroupCEHandler(groupidx)
{
if(uroPlacesGroupsCollapsed[groupidx] === false)
{
document.getElementById('_uroPlacesGroup-'+groupidx).style.display = "block";
document.getElementById('_uroPlacesGroupState-'+groupidx).className = "fa fa-minus-square-o";
}
else
{
document.getElementById('_uroPlacesGroup-'+groupidx).style.display = "none";
document.getElementById('_uroPlacesGroupState-'+groupidx).className = "fa fa-plus-square-o";
}
}
function uroPlacesGroupCollapseExpand()
{
let groupidx = this.id.slice(21);
if(uroPlacesGroupsCollapsed[groupidx] === true) uroPlacesGroupsCollapsed[groupidx] = false;
else uroPlacesGroupsCollapsed[groupidx] = true;
uroPlacesGroupCEHandler(groupidx);
return false;
}
function uroGetMarkerIDs(markerType)
{
let idList = [];
let mtList = uroGetMarkerTypeList(markerType);
while(mtList.length > 0)
{
let mt = mtList.pop();
if(uroLayers.layers[mt].mf !== null)
{
// Refresh .mf, as this is no longer guaranteed to have been set up as a reference to
// whatever's in the current layer stack...
uroLayers.layers[mt].mf = uroLayers.GetMarkersOrFeatures(mt);
for(let i = 0; i < uroLayers.layers[mt].mf.length; ++i)
{
let dID = uroLayers.layers[mt].mf[i]?.element?.attributes['data-id']?.value;
if(dID === undefined)
{
dID = uroLayers.layers[mt].mf[i]?.attributes?.wazeFeature?.id;
}
if(dID !== undefined)
{
if(typeof dID === "string")
{
dID = parseInt(dID);
}
idList.push(dID);
}
}
}
}
return idList;
}
function uroGetAttributes(markerType, markerID)
{
if(markerType == uroLayersFT.ID.UR) return sdk.DataModel.MapUpdateRequests.getById({mapUpdateRequestId: markerID});
if(markerType == uroLayersFT.ID.MP) return sdk.DataModel.MapProblems.getById({mapProblemId: markerID});
if(markerType == uroLayersFT.ID.VPUR) return sdk.DataModel.Venues.getById({venueId: markerID});
if(markerType == uroLayersFT.ID.SegSug) return sdk.DataModel.EditSuggestions.getById({editSuggestionId: markerID});
return null;
}
function uroGetMarker(markerType, markerID)
{
if(typeof(markerID) === 'string')
{
markerID = parseInt(markerID);
}
let retval = null;
if(markerID !== null)
{
retval = uroFindMarkerOnLayer(markerType, markerID);
}
return retval;
}
function uroGetMarkerTypeList(markerType)
{
let mtList = [];
if(markerType === uroLayersFT.ID.VPUR)
{
mtList.push(uroLayersFT.ID.PUR);
mtList.push(uroLayersFT.ID.PPUR);
mtList.push(uroLayersFT.ID.RPUR);
}
else
{
mtList.push(markerType);
}
return mtList;
}
function uroFindMarkerOnLayer(markerType, markerID)
{
retval = null;
let mtList = uroGetMarkerTypeList(markerType);
while(mtList.length > 0)
{
let mt = mtList.pop();
retval = sdk.Map.getFeatureDomElement({featureId: markerID, layerName: uroLayers.layers[mt].name});
if(retval !== null)
{
break;
}
}
return retval;
}
uroInit.Initialise();