Service bank feature removed from front, back and e2e testing mostly commented out in case need to add back again but in some places such as db schema it had to be removed entirely so refer here if adding back in again
949 lines
28 KiB
JavaScript
949 lines
28 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];
|
|
}
|
|
}
|
|
},
|
|
///////////////////////////////
|
|
// DEEP COPY FOR API UPDATE
|
|
// Deep copy an object skipping all *Viz and named properties from object
|
|
//
|
|
deepCopySkip: function(source, skipNames) {
|
|
if (skipNames == null) {
|
|
skipNames = [];
|
|
}
|
|
let o = {};
|
|
for (let key in source) {
|
|
if (
|
|
!key.endsWith("Viz") &&
|
|
!skipNames.some(x => x == key) &&
|
|
source.hasOwnProperty(key)
|
|
) {
|
|
o[key] = source[key];
|
|
}
|
|
}
|
|
return o;
|
|
},
|
|
/**
|
|
* 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;
|
|
},
|
|
///////////////////////////////
|
|
// ROUNDING
|
|
// //https://medium.com/swlh/how-to-round-to-a-certain-number-of-decimal-places-in-javascript-ed74c471c1b8
|
|
roundAccurately: function(number, decimalPlaces) {
|
|
if (!number || number == 0 || number == NaN) {
|
|
return number;
|
|
}
|
|
let wasNegative = number < 0;
|
|
if (wasNegative) {
|
|
number = Math.abs(number); //make sure it's positive because rounding negative numbers is weird in JS
|
|
}
|
|
number = Number(
|
|
Math.round(number + "e" + decimalPlaces) + "e-" + decimalPlaces
|
|
);
|
|
if (wasNegative) {
|
|
number = 0 - number;
|
|
}
|
|
return number;
|
|
},
|
|
///////////////////////////////
|
|
// 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.WorkOrderItemOutsideService:
|
|
return "$ayiLuggageCart";
|
|
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
|
|
};
|