908 lines
27 KiB
JavaScript
908 lines
27 KiB
JavaScript
/* Xeslint-disable */
|
|
|
|
/////////////////////////////////
|
|
// General utility library
|
|
//
|
|
|
|
const icons = {
|
|
image: "$ayiFileImage",
|
|
pdf: "$ayiFilePdf",
|
|
word: "$ayiFileWord",
|
|
powerpoint: "$ayiFilePowerpoint",
|
|
excel: "$ayiFileExcel",
|
|
csv: "$ayiFileCsv",
|
|
audio: "$ayiFileAudio",
|
|
video: "$ayiFileVidio",
|
|
archive: "$ayiFileArchive",
|
|
code: "$ayiFileCode",
|
|
text: "$ayiFileAlt",
|
|
file: "$ayiFile"
|
|
};
|
|
const mimeTypes = {
|
|
"image/gif": icons.image,
|
|
"image/jpeg": icons.image,
|
|
"image/png": icons.image,
|
|
"image/webp": icons.image,
|
|
|
|
"application/pdf": icons.pdf,
|
|
|
|
"application/msword": icons.word,
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
icons.word,
|
|
|
|
"application/mspowerpoint": icons.powerpoint,
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
|
icons.powerpoint,
|
|
|
|
"application/msexcel": icons.excel,
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
|
icons.excel,
|
|
|
|
"text/csv": icons.csv,
|
|
|
|
"audio/aac": icons.audio,
|
|
"audio/wav": icons.audio,
|
|
"audio/mpeg": icons.audio,
|
|
"audio/mp4": icons.audio,
|
|
"audio/ogg": icons.audio,
|
|
|
|
"video/x-msvideo": icons.video,
|
|
"video/mpeg": icons.video,
|
|
"video/mp4": icons.video,
|
|
"video/ogg": icons.video,
|
|
"video/quicktime": icons.video,
|
|
"video/webm": icons.video,
|
|
|
|
"application/gzip": icons.archive,
|
|
"application/zip": icons.archive,
|
|
"application/x-tar": icons.archive,
|
|
|
|
"text/css": icons.code,
|
|
"text/html": icons.code,
|
|
"text/javascript": icons.code,
|
|
"application/javascript": icons.code,
|
|
|
|
"text/plain": icons.text,
|
|
"text/richtext": icons.text,
|
|
"text/rtf": icons.text,
|
|
"application/rtf": icons.text,
|
|
"application/json": icons.text
|
|
};
|
|
|
|
const extensions = {
|
|
gif: icons.image,
|
|
jpeg: icons.image,
|
|
jpg: icons.image,
|
|
png: icons.image,
|
|
webp: icons.image,
|
|
|
|
pdf: icons.pdf,
|
|
|
|
doc: icons.word,
|
|
docx: icons.word,
|
|
|
|
ppt: icons.powerpoint,
|
|
pptx: icons.powerpoint,
|
|
|
|
xls: icons.excel,
|
|
xlsx: icons.excel,
|
|
|
|
csv: icons.csv,
|
|
|
|
aac: icons.audio,
|
|
mp3: icons.audio,
|
|
ogg: icons.audio,
|
|
|
|
avi: icons.video,
|
|
flv: icons.video,
|
|
mkv: icons.video,
|
|
mp4: icons.video,
|
|
|
|
gz: icons.archive,
|
|
zip: icons.archive,
|
|
tar: icons.archive,
|
|
"7z": icons.archive,
|
|
|
|
css: icons.code,
|
|
html: icons.code,
|
|
js: icons.code,
|
|
|
|
txt: icons.text,
|
|
json: icons.text,
|
|
rtf: icons.text
|
|
};
|
|
|
|
export default {
|
|
///////////////////////////////
|
|
// CLEAN OBJECT
|
|
// Clear all properties from object without resorting to assigning a new object (o={})
|
|
// which can be problematic in some cases (IE bugs, watched data items in forms etc)
|
|
|
|
removeAllPropertiesFromObject: function(o) {
|
|
for (let variableKey in o) {
|
|
if (o.hasOwnProperty(variableKey)) {
|
|
delete o[variableKey];
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Copy a string to clipboard
|
|
* @param {String} string The string to be copied to clipboard
|
|
* @return {Boolean} returns a boolean correspondent to the success of the copy operation.
|
|
* Modified from an example here: https://stackoverflow.com/a/53951634/8939
|
|
* Basically a fallback if navigator.clipboard is not available
|
|
*/
|
|
copyToClipboard: function(string) {
|
|
let textarea;
|
|
let result;
|
|
|
|
if (navigator && navigator.clipboard) {
|
|
navigator.clipboard.writeText(string);
|
|
} else {
|
|
try {
|
|
textarea = document.createElement("textarea");
|
|
textarea.setAttribute("readonly", true);
|
|
textarea.setAttribute("contenteditable", true);
|
|
textarea.style.position = "fixed"; // prevent scroll from jumping to the bottom when focus is set.
|
|
textarea.value = string;
|
|
|
|
document.body.appendChild(textarea);
|
|
|
|
textarea.focus();
|
|
textarea.select();
|
|
|
|
const range = document.createRange();
|
|
range.selectNodeContents(textarea);
|
|
|
|
const sel = window.getSelection();
|
|
sel.removeAllRanges();
|
|
sel.addRange(range);
|
|
|
|
textarea.setSelectionRange(0, textarea.value.length);
|
|
result = document.execCommand("copy");
|
|
} catch (err) {
|
|
result = null;
|
|
} finally {
|
|
document.body.removeChild(textarea);
|
|
}
|
|
|
|
// manual copy fallback using prompt
|
|
if (!result) {
|
|
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
|
const copyHotkey = isMac ? "⌘C" : "CTRL+C";
|
|
result = prompt(`Press ${copyHotkey}`, string);
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
///////////////////////////////
|
|
// CLEAN TAG NAME
|
|
// Clean up a tag with same rules as server
|
|
//
|
|
normalizeTag: function(tagName) {
|
|
/*
|
|
SERVER VERSION
|
|
public static string NormalizeTag(string inObj)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(inObj)) return null;
|
|
//Must be lowercase per rules
|
|
//This may be naive when we get international cust omers but for now supporting utf-8 and it appears it's safe to do this with unicode
|
|
inObj = inObj.ToLowerInvariant();
|
|
//No spaces in tags, replace with dashes
|
|
inObj = inObj.Replace(" ", "-");
|
|
//Remove multiple dash sequences
|
|
inObj = System.Text.RegularExpressions.Regex.Replace(inObj, "-+", "-");
|
|
//Ensure doesn't start or end with a dash
|
|
inObj = inObj.Trim('-');
|
|
//No longer than 255 characters
|
|
inObj = StringUtil.MaxLength(inObj, 255);
|
|
return inObj;
|
|
}
|
|
*/
|
|
//de-lodash
|
|
//kebab case takes care of all the things we need for tags in one go
|
|
// tagName = window.$gz. _.kebabCase(tagName);
|
|
|
|
if (!tagName || tagName == "") {
|
|
return null;
|
|
}
|
|
tagName = tagName.toLowerCase();
|
|
|
|
//spaces to dashes
|
|
tagName = tagName.replace(/ /gi, "-");
|
|
|
|
//multiple dashes to single dashes
|
|
tagName = tagName.replace(/-+/g, "-");
|
|
|
|
//ensure doesn't start or end with a dash
|
|
tagName = this.trimSpecific(tagName, "-");
|
|
|
|
//No longer than 255 characters
|
|
tagName = tagName.length > 255 ? tagName.substr(0, 255 - 1) : tagName;
|
|
|
|
return tagName;
|
|
},
|
|
///////////////////////////////
|
|
// Quick hash for trivial purposes
|
|
// not cryptographic
|
|
// https://stackoverflow.com/a/7616484/8939
|
|
//
|
|
quickHash: function(theString) {
|
|
let hash = 0,
|
|
i,
|
|
chr;
|
|
if (theString.length === 0) return hash;
|
|
for (i = 0; i < theString.length; i++) {
|
|
chr = theString.charCodeAt(i);
|
|
hash = (hash << 5) - hash + chr;
|
|
hash |= 0; // Convert to 32bit integer
|
|
}
|
|
return hash;
|
|
},
|
|
|
|
////////////////////////////////////////
|
|
// Random password / login generator
|
|
// https://stackoverflow.com/a/51540480/8939
|
|
// using 32 character (128 bit) as default
|
|
//
|
|
getRandomPassword: function() {
|
|
let length = 32,
|
|
wishlist = "0123456789abcdefghijkmnopqrstuvwxyz";
|
|
|
|
return Array.from(crypto.getRandomValues(new Uint32Array(length)))
|
|
.map(x => wishlist[x % wishlist.length])
|
|
.join("");
|
|
},
|
|
///////////////////////////////
|
|
// CONVERT STRING TO BOOLEAN
|
|
// https://stackoverflow.com/a/1414175/8939
|
|
//
|
|
stringToBoolean: function(string) {
|
|
switch (string.toLowerCase().trim()) {
|
|
case "true":
|
|
case "yes":
|
|
case "1":
|
|
return true;
|
|
case "false":
|
|
case "no":
|
|
case "0":
|
|
case null:
|
|
return false;
|
|
default:
|
|
return Boolean(string);
|
|
}
|
|
}, ///////////////////////////////
|
|
// CONVERT STRING TO FLOAT
|
|
// https://stackoverflow.com/a/9409894/8939
|
|
//
|
|
stringToFloat: function(string) {
|
|
//null or empty then zero
|
|
if (!string) {
|
|
return 0;
|
|
}
|
|
|
|
//A number already then parse and return
|
|
if (this.isNumeric(string)) {
|
|
if (string === NaN) {
|
|
return 0;
|
|
}
|
|
return parseFloat(string);
|
|
}
|
|
|
|
//Not a string at all?
|
|
if (!this.isString(string)) {
|
|
return 0;
|
|
}
|
|
|
|
let ret = parseFloat(string.replace(/[^\d.-]/g, ""));
|
|
if (ret == NaN) {
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
///////////////////////////////
|
|
// Is negative number
|
|
//
|
|
//
|
|
isNegative: function(v) {
|
|
//null or empty then zero
|
|
if (!v || v == 0 || v == NaN) {
|
|
return false;
|
|
}
|
|
return parseFloat(v) < 0;
|
|
},
|
|
///////////////////////////////
|
|
// Splice a string
|
|
//changes the content of a string by removing a range of
|
|
// characters and/or adding new characters.
|
|
//
|
|
// @param {String} source string
|
|
// @param {number} start Index at which to start changing the string.
|
|
// @param {number} delCount An integer indicating the number of old chars to remove.
|
|
// @param {string} newSubStr The String that is spliced in.
|
|
// @return {string} A new string with the spliced substring.
|
|
stringSplice: function(source, start, delCount, newSubStr) {
|
|
if (source == null || source == "") {
|
|
if (newSubStr) {
|
|
return newSubStr;
|
|
}
|
|
return "";
|
|
}
|
|
return (
|
|
source.slice(0, start) +
|
|
newSubStr +
|
|
source.slice(start + Math.abs(delCount))
|
|
);
|
|
},
|
|
///////////////////////////////
|
|
// Truncate a string
|
|
//truncates and adds ellipses
|
|
//
|
|
// @param {String} source string
|
|
// @param {number} length desired
|
|
// @return {string} A new string truncated with ellipses at end
|
|
truncateString: function(s, len) {
|
|
if (this.stringIsNullOrEmpty(s)) {
|
|
return s;
|
|
}
|
|
if (s.length > len) {
|
|
return s.substring(0, len) + "...";
|
|
} else {
|
|
return s;
|
|
}
|
|
},
|
|
///////////////////////////////
|
|
// Format tags for display
|
|
//
|
|
//
|
|
// @param {String} tags raw from server
|
|
// @return {string} A new string with the tags formatted or an empty string if no tags
|
|
formatTags: function(tags) {
|
|
if (tags && tags.length > 0) {
|
|
return tags.join(", ");
|
|
}
|
|
return "";
|
|
},
|
|
///////////////////////////////
|
|
// ICON FOR *ALL* OBJECT TYPES
|
|
//(used for search results and event log / history)
|
|
//NOTE: Any object type could appear in event log, they all need to be supported where possible
|
|
//CoreBizObject add here
|
|
iconForType: function(ayaType) {
|
|
switch (ayaType) {
|
|
case window.$gz.type.NoType:
|
|
case null:
|
|
return "$ayiGenderless";
|
|
case window.$gz.type.Global:
|
|
return "$ayiGlobe";
|
|
case window.$gz.type.Widget:
|
|
return "$ayiVial";
|
|
case window.$gz.type.User:
|
|
return "$ayiUser";
|
|
case window.$gz.type.ServerState:
|
|
return "$ayiDoorOpen";
|
|
case window.$gz.type.License:
|
|
return "$ayiTicket";
|
|
case window.$gz.type.LogFile:
|
|
return "$ayiGlasses";
|
|
case window.$gz.type.PickListTemplate:
|
|
return "$ayiPencilRuler";
|
|
case window.$gz.type.Customer:
|
|
return "$ayiAddressCard";
|
|
case window.$gz.type.ServerJob:
|
|
return "$ayiRobot";
|
|
case window.$gz.type.Contract:
|
|
return "$ayiFileContract";
|
|
case window.$gz.type.TrialSeeder:
|
|
return "$ayiSeedling";
|
|
case window.$gz.type.Metrics:
|
|
return "$ayiFileMedicalAlt";
|
|
case window.$gz.type.Translation:
|
|
return "$ayiLanguage";
|
|
case window.$gz.type.UserOptions:
|
|
return "$ayiUserCog";
|
|
case window.$gz.type.HeadOffice:
|
|
return "$ayiSitemap";
|
|
case window.$gz.type.LoanUnit:
|
|
return "$ayiPlug";
|
|
case window.$gz.type.FileAttachment:
|
|
return "$ayiPaperclip";
|
|
case window.$gz.type.DataListSavedFilter:
|
|
return "$ayiFilter";
|
|
case window.$gz.type.FormCustom:
|
|
return "$ayiCustomize";
|
|
case window.$gz.type.Part:
|
|
return "$ayiBoxes";
|
|
case window.$gz.type.PartWarehouse:
|
|
return "$ayiWarehouse";
|
|
case window.$gz.type.PartAssembly:
|
|
return "$ayiObjectGroup";
|
|
case window.$gz.type.PM:
|
|
return "$ayiBusinessTime";
|
|
case window.$gz.type.PMItem:
|
|
return "$ayiBusinessTime";
|
|
case window.$gz.type.PMTemplate:
|
|
return "$ayiStamp";
|
|
case window.$gz.type.PMTemplateItem:
|
|
return "$ayiStamp";
|
|
case window.$gz.type.Project:
|
|
return "$ayiLayerGroup";
|
|
case window.$gz.type.PurchaseOrder:
|
|
return "$ayiTruckLoading";
|
|
case window.$gz.type.Quote:
|
|
return "$ayiPencilAlt";
|
|
case window.$gz.type.QuoteItem:
|
|
return "$ayiWrench";
|
|
case window.$gz.type.QuoteTemplate:
|
|
return "$ayiStamp";
|
|
case window.$gz.type.QuoteTemplateItem:
|
|
return "$ayiStamp";
|
|
case window.$gz.type.Unit:
|
|
return "$ayiFan";
|
|
case window.$gz.type.UnitModel:
|
|
return "$ayiDiceD20";
|
|
case window.$gz.type.Vendor:
|
|
return "$ayiStore";
|
|
case window.$gz.type.WorkOrder:
|
|
return "$ayiTools";
|
|
case window.$gz.type.WorkOrderItem:
|
|
return "$ayiWrench";
|
|
case window.$gz.type.WorkOrderItemExpense:
|
|
return "$ayiMoneyBillWave";
|
|
case window.$gz.type.WorkOrderItemLabor:
|
|
return "$ayiHammer";
|
|
case window.$gz.type.WorkOrderItemLoan:
|
|
return "$ayiPlug";
|
|
case window.$gz.type.WorkOrderItemPart:
|
|
return "$ayiBoxes";
|
|
case window.$gz.type.WorkOrderItemPartRequest:
|
|
return "$ayiParachuteBox";
|
|
case window.$gz.type.WorkOrderItemScheduledUser:
|
|
return "$ayiUserClock";
|
|
case window.$gz.type.WorkOrderItemTask:
|
|
case window.$gz.type.TaskGroup:
|
|
return "$ayiTasks";
|
|
case window.$gz.type.WorkOrderItemTravel:
|
|
return "$ayiTruckMonster";
|
|
case window.$gz.type.WorkOrderItemUnit:
|
|
return "$ayiFan";
|
|
case window.$gz.type.WorkOrderTemplate:
|
|
return "$ayiStamp";
|
|
case window.$gz.type.WorkOrderTemplateItem:
|
|
return "$ayiStamp";
|
|
case window.$gz.type.Backup:
|
|
return "$ayiFileArchive";
|
|
case window.$gz.type.Notification:
|
|
return "$ayiBell";
|
|
case window.$gz.type.NotifySubscription:
|
|
return "$ayiBullhorn";
|
|
case window.$gz.type.Reminder:
|
|
return "$ayiStickyNote";
|
|
case window.$gz.type.UnitMeterReading:
|
|
return "$ayiWeight";
|
|
case window.$gz.type.CustomerServiceRequest:
|
|
return "$ayiConciergeBell";
|
|
case window.$gz.type.ServiceBank:
|
|
return "$ayiCarBattery";
|
|
case window.$gz.type.OpsNotificationSettings:
|
|
return "$ayiBullhorn";
|
|
case window.$gz.type.Report:
|
|
return "$ayiThList";
|
|
case window.$gz.type.DashboardView:
|
|
return "$ayiTachometer";
|
|
case window.$gz.type.CustomerNote:
|
|
return "$ayiClipboard";
|
|
case window.$gz.type.Memo:
|
|
return "$ayiInbox";
|
|
case window.$gz.type.Review:
|
|
return "$ayiCalendarCheck";
|
|
case window.$gz.type.ServiceRate:
|
|
return "$ayiCalculator";
|
|
case window.$gz.type.TravelRate:
|
|
return "$ayiCalculator";
|
|
case window.$gz.type.TaxCode:
|
|
return "$ayiPercent";
|
|
case window.$gz.type.WorkOrderStatus:
|
|
return "$ayiFlag";
|
|
|
|
//scroll icon is good one for something
|
|
default:
|
|
return null;
|
|
}
|
|
},
|
|
//https://gist.github.com/colemanw/9c9a12aae16a4bfe2678de86b661d922
|
|
iconForFile: function(fileName, mimeType) {
|
|
// List of official MIME Types: http://www.iana.org/assignments/media-types/media-types.xhtml
|
|
|
|
let extension = null;
|
|
if (fileName && fileName.includes(".")) {
|
|
extension = fileName.split(".").pop();
|
|
extension = extension.toLowerCase();
|
|
}
|
|
if (!extension && !mimeType) {
|
|
console.log(
|
|
"gzutil:iconForFile -> No mime or extension for " +
|
|
fileName +
|
|
" " +
|
|
mimeType
|
|
);
|
|
return "$ayiFile";
|
|
}
|
|
|
|
if (!mimeType) {
|
|
mimeType = "";
|
|
}
|
|
mimeType = mimeType.toLowerCase();
|
|
|
|
let iconFromExtension = extensions[extension];
|
|
let iconFromMIME = mimeTypes[mimeType];
|
|
|
|
if (iconFromMIME) {
|
|
return iconFromMIME;
|
|
}
|
|
if (iconFromExtension) {
|
|
return iconFromExtension;
|
|
}
|
|
|
|
return "$ayiFile";
|
|
},
|
|
///////////////////////////////////////////////
|
|
// attempt to detect image extension name
|
|
//
|
|
isImageAttachment: function(fileName, mimeType) {
|
|
return this.iconForFile(fileName, mimeType) == "$ayiFileImage";
|
|
},
|
|
///////////////////////////////////////////////
|
|
// Sleep async
|
|
//
|
|
sleepAsync: function(milliseconds) {
|
|
// eslint-disable-next-line
|
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
},
|
|
///////////////////////////////////////////////
|
|
// sortByKey lodash "sortBy" replacement
|
|
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_sortby-and-_orderby
|
|
//usage:
|
|
// The native sort modifies the array in place. `_.orderBy` and `_.sortBy` do not, so we use `.concat()` to
|
|
// copy the array, then sort.
|
|
// fruits.concat().sort(sortBy("name"));
|
|
// => [{name:"apple", amount: 4}, {name:"banana", amount: 2}, {name:"mango", amount: 1}, {name:"pineapple", amount: 2}]
|
|
sortByKey: key => {
|
|
return (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0);
|
|
},
|
|
///////////////////////////////////////////////
|
|
// "has" lodash replacement
|
|
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_has
|
|
//
|
|
has: function(obj, key) {
|
|
var keyParts = key.split(".");
|
|
return (
|
|
!!obj &&
|
|
(keyParts.length > 1
|
|
? this.has(obj[key.split(".")[0]], keyParts.slice(1).join("."))
|
|
: hasOwnProperty.call(obj, key))
|
|
);
|
|
},
|
|
///////////////////////////////////////////////
|
|
// Check if object is empty
|
|
//
|
|
objectIsEmpty: function(obj) {
|
|
//https://stackoverflow.com/a/4994265/8939
|
|
return !obj || Object.keys(obj).length === 0;
|
|
},
|
|
///////////////////////////////////////////////
|
|
// Trim specific character from start and end
|
|
// https://stackoverflow.com/a/55292366/8939
|
|
//
|
|
trimSpecific: function trim(str, ch) {
|
|
var start = 0,
|
|
end = str.length;
|
|
while (start < end && str[start] === ch) ++start;
|
|
while (end > start && str[end - 1] === ch) --end;
|
|
return start > 0 || end < str.length ? str.substring(start, end) : str;
|
|
},
|
|
///////////////////////////////////////////////
|
|
// is numeric replacement for lodash
|
|
// https://stackoverflow.com/a/52986361/8939
|
|
//
|
|
isNumeric: function(n) {
|
|
//lodash isNumber returned false if it's a string and that's what the rest of the code expects even though it's parseable to a number
|
|
return !this.isString(n) && !isNaN(parseFloat(n)) && isFinite(n);
|
|
},
|
|
///////////////////////////////////////////////
|
|
// is string replacement for lodash
|
|
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_isString
|
|
//
|
|
isString: function(str) {
|
|
//modified from above, due to bug (I think)
|
|
//posted case here: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore/issues/304
|
|
if (str == null) {
|
|
return false;
|
|
}
|
|
|
|
if (str == "") {
|
|
return true;
|
|
}
|
|
let temp = str.valueOf();
|
|
if (typeof temp === "string") {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
///////////////////////////////////////////////
|
|
//
|
|
//
|
|
//
|
|
stringIsNullOrEmpty: function(str) {
|
|
if (str === null || str === undefined) {
|
|
return true;
|
|
}
|
|
if (this.isString(str)) {
|
|
if (str.trim() == "") {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
///////////////////////////////////////////////
|
|
// is Boolean replacement for lodash
|
|
// https://stackoverflow.com/a/43718478/8939
|
|
//
|
|
isBoolean: function(obj) {
|
|
return obj === true || obj === false || typeof variable === "boolean";
|
|
},
|
|
///////////////////////////////////////////////
|
|
// parse to number or null if not a number
|
|
// used because route params can turn into strings
|
|
// on their own
|
|
//
|
|
stringToIntOrNull: function(n) {
|
|
const ret = Number.parseInt(n, 10);
|
|
if (Number.isNaN(ret)) {
|
|
return null;
|
|
}
|
|
return ret;
|
|
},
|
|
///////////////////////////////////////////////
|
|
// Simple array equality comparison
|
|
// (will NOT work on arrays of objects)
|
|
// Array order is relevant here as they are not sorted
|
|
// change of order will equal change of array
|
|
// as this is required for datatable sortby
|
|
//
|
|
isEqualArraysOfPrimitives: function(a, b) {
|
|
if (a === b) return true;
|
|
if (a == null || b == null) return false;
|
|
if (a.length !== b.length) return false;
|
|
|
|
// If you don't care about the order of the elements inside
|
|
// the array, you should sort both arrays here.
|
|
// Please note that calling sort on an array will modify that array.
|
|
// you might want to clone your array first.
|
|
|
|
for (var i = 0; i < a.length; ++i) {
|
|
if (a[i] !== b[i]) return false;
|
|
}
|
|
return true;
|
|
},
|
|
///////////////////////////////////////////////
|
|
// Use geolocation api to attempt to get current location
|
|
// try high accuracy first and downgrade if unavailable
|
|
//https://www.openstreetmap.org/?mlat=48.3911&mlon=-124.7353#map=12/48.3910/-124.7353
|
|
//https://www.openstreetmap.org/#map=18/49.68155/-125.00435
|
|
//https://www.openstreetmap.org/?mlat=49.71236&mlon=-124.96961#map=17/49.71236/-124.96961
|
|
//https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393
|
|
getGeoLocation: async function() {
|
|
return new Promise((resolve, reject) => {
|
|
navigator.geolocation.getCurrentPosition(
|
|
function successHigh(pos) {
|
|
resolve({
|
|
latitude: pos.coords.latitude,
|
|
longitude: pos.coords.longitude
|
|
});
|
|
},
|
|
function error(err) {
|
|
//if here due to timeout getting high accuracy then try again with low accuracy
|
|
if (error.code == error.TIMEOUT) {
|
|
navigator.geolocation.getCurrentPosition(
|
|
function successLow(pos) {
|
|
resolve({
|
|
latitude: pos.coords.latitude,
|
|
longitude: pos.coords.longitude
|
|
});
|
|
},
|
|
function error(err) {
|
|
reject(
|
|
new Error(
|
|
`ERROR getting location(low_accuracy: ${err.code}): ${err.message}`
|
|
)
|
|
);
|
|
},
|
|
{ maximumAge: 600000, timeout: 10000, enableHighAccuracy: false }
|
|
);
|
|
return;
|
|
}
|
|
|
|
reject(
|
|
new Error(
|
|
`ERROR GETTING LOCATION(high_accuracy:${err.code}): ${err.message}`
|
|
)
|
|
);
|
|
},
|
|
{ maximumAge: 600000, timeout: 5000, enableHighAccuracy: true }
|
|
);
|
|
});
|
|
},
|
|
///////////////////////////////////////////////
|
|
// Open map url
|
|
//
|
|
//
|
|
viewGeoLocation: function(obj) {
|
|
/*
|
|
{
|
|
latitude: m.vm.obj.latitude,
|
|
longitude: m.vm.obj.longitude,
|
|
address: m.vm.obj.address || m.vm.obj.postAddress,
|
|
city: m.vm.obj.city || m.vm.obj.postCity,
|
|
region: m.vm.obj.region || m.vm.obj.postRegion,
|
|
country: m.vm.obj.country || m.vm.obj.postCountry,
|
|
postCode: m.vm.obj.postCode
|
|
}
|
|
*/
|
|
|
|
let hasGeo =
|
|
obj.latitude != null &&
|
|
obj.latitude != 0 &&
|
|
obj.longitude != null &&
|
|
obj.longitude != 0;
|
|
|
|
let hasAddress =
|
|
!this.stringIsNullOrEmpty(obj.address) &&
|
|
!this.stringIsNullOrEmpty(obj.city) &&
|
|
!this.stringIsNullOrEmpty(obj.region) &&
|
|
!this.stringIsNullOrEmpty(obj.country) &&
|
|
!this.stringIsNullOrEmpty(obj.postCode);
|
|
|
|
if (!hasGeo && !hasAddress) {
|
|
return;
|
|
}
|
|
|
|
let mapUrl = window.$gz.store.state.userOptions.mapUrlTemplate;
|
|
|
|
//No pre-set?
|
|
if (!mapUrl || mapUrl == "") {
|
|
mapUrl =
|
|
"https://www.google.com/maps/search/?api=1&query={ayaddress}<|>https://www.google.com/maps/search/?api=1&query={aylatitude},{aylongitude}";
|
|
}
|
|
|
|
let geoMapUrl = null;
|
|
let addressMapUrl = null;
|
|
|
|
//Parse the map url
|
|
let mapUrls = [mapUrl];
|
|
if (mapUrl.includes("<|>")) {
|
|
mapUrls = mapUrl.split("<|>");
|
|
}
|
|
|
|
mapUrls.forEach(z => {
|
|
if (!geoMapUrl && z.includes("{aylatitude}")) {
|
|
geoMapUrl = z;
|
|
}
|
|
if (!addressMapUrl && z.includes("{ayaddress}")) {
|
|
addressMapUrl = z;
|
|
}
|
|
});
|
|
|
|
//decide which map to use here, favor geocode
|
|
if (hasGeo && geoMapUrl) {
|
|
//geo view
|
|
mapUrl = geoMapUrl;
|
|
mapUrl = mapUrl.split("{aylatitude}").join(obj.latitude);
|
|
mapUrl = mapUrl.split("{aylongitude}").join(obj.longitude);
|
|
} else if (hasAddress && addressMapUrl) {
|
|
mapUrl = addressMapUrl;
|
|
//compile address fields together
|
|
//order street to country seems to be standard
|
|
//note, if google need plus symbol delimiter, if bing, need comma delimiter
|
|
//but both might accept one big string space delimited and url encoded so test that on all first
|
|
let delimiter = " ";
|
|
let q = "";
|
|
if (obj.address) {
|
|
q += obj.address + delimiter;
|
|
}
|
|
if (obj.city) {
|
|
q += obj.city + delimiter;
|
|
}
|
|
|
|
if (obj.region) {
|
|
q += obj.region + delimiter;
|
|
}
|
|
|
|
if (obj.country) {
|
|
q += obj.country + delimiter;
|
|
}
|
|
|
|
if (obj.postCode) {
|
|
q += obj.postCode + delimiter;
|
|
}
|
|
if (q.length > 1) {
|
|
q = q.substring(0, q.length - 1);
|
|
}
|
|
//url encode the query
|
|
q = encodeURIComponent(q);
|
|
mapUrl = mapUrl.split("{ayaddress}").join(q);
|
|
} else {
|
|
throw new Error(
|
|
"View map: error - no matching mapurl / address / geo coordinates set for display, nothing to view"
|
|
);
|
|
}
|
|
window.open(mapUrl, "map");
|
|
//This is not valid to do as some platforms don't open a new web browser window
|
|
//but rather a map application in which case this is null and throws up the exception even though it's working
|
|
// if (window.open(mapUrl, "map") == null) {
|
|
// throw new Error(
|
|
// "Problem displaying map in new window. Browser must allow pop-ups to view maps; check your browser setting"
|
|
// );
|
|
// }
|
|
},
|
|
///////////////////////////////////////////////
|
|
// Online mapping service url formats
|
|
//
|
|
//
|
|
mapProviderUrls: function() {
|
|
return [
|
|
{
|
|
name: "Apple",
|
|
value:
|
|
"http://maps.apple.com/?q={ayaddress}<|>http://maps.apple.com/?ll={aylatitude},{aylongitude}"
|
|
},
|
|
{
|
|
name: "Bing",
|
|
value:
|
|
"https://bing.com/maps/default.aspx?where1={ayaddress}<|>https://bing.com/maps/default.aspx?cp={aylatitude}~{aylongitude}&lvl=17&style=r&sp=point.{aylatitude}_{aylongitude}"
|
|
},
|
|
{
|
|
name: "Google",
|
|
value:
|
|
"https://www.google.com/maps/search/?api=1&query={ayaddress}<|>https://www.google.com/maps/search/?api=1&query={aylatitude},{aylongitude}"
|
|
},
|
|
{
|
|
name: "MapQuest",
|
|
value:
|
|
"https://mapquest.com/?center={ayaddress}&zoom=17<|>https://mapquest.com/?center={aylatitude},{aylongitude}&zoom=17"
|
|
},
|
|
{
|
|
name: "Open Street Map",
|
|
value:
|
|
"https://www.openstreetmap.org/search?query={ayaddress}<|>https://www.openstreetmap.org/?mlat={aylatitude}&mlon={aylongitude}#map=17/{aylatitude}/{aylongitude}"
|
|
},
|
|
{
|
|
name: "geo URI",
|
|
value: "geo:{aylatitude},{aylongitude}"
|
|
},
|
|
{
|
|
name: "Waze",
|
|
value:
|
|
"https://waze.com/ul?q={ayaddress}<|>https://www.waze.com/ul?ll={aylatitude},{aylongitude}&navigate=yes&zoom=17"
|
|
},
|
|
{
|
|
name: "Yandex",
|
|
value:
|
|
"https://yandex.ru/maps/?mode=search&text={ayaddress}&z=17<|>https://yandex.ru/maps/?ll={aylatitude},{aylongitude}&z=12&l=map"
|
|
}
|
|
];
|
|
}
|
|
|
|
/**
|
|
*
|
|
*
|
|
*/
|
|
|
|
//new functions above here
|
|
};
|