@esri/solution-common
Version:
Provides general helper functions for @esri/solution.js.
959 lines • 32.7 kB
JavaScript
;
/** @license
* Copyright 2018 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.uniqueStringList = exports.regExTest = exports.getTemplateById = exports.cleanLayerId = exports.cleanLayerBasedItemId = exports.cleanItemId = exports.hasDatasource = exports.globalStringReplace = exports.getUniqueTitle = exports.hasTypeKeyword = exports.hasAnyKeyword = exports.getUTCTimestamp = exports.setProp = exports.setCreateProp = exports.idTest = exports.getPropWithDefault = exports.getProps = exports.getProp = exports.getIDs = exports.getSubgroupIds = exports.failWithIds = exports.fail = exports.deleteProps = exports.deleteProp = exports.deleteItemProps = exports.sanitizeJSONAndReportChanges = exports.compareJSONProperties = exports.compareJSONNoEmptyStrings = exports.compareJSON = exports.cloneObject = exports.saveBlobAsFile = exports.jsonToJson = exports.jsonToFile = exports.jsonToBlob = exports.getTemplatedIds = exports.getSpecifiedWordRegEx = exports.getAgoIdTemplateRegEx = exports.getAgoIdRegEx = exports.generateGUID = exports.generateEmptyCreationResponse = exports.delay = exports.dedupe = exports.createShortId = exports.createLongId = exports.convertIModel = exports.checkUrlPathTermination = exports.blobToText = exports.blobToFile = exports.blobToJson = exports.appendQueryParam = void 0;
exports._padPositiveNum = exports._getRandomNumberInRange = void 0;
/**
* Provides general helper functions.
*
* @module generalHelpers
*/
const hub_common_1 = require("@esri/hub-common");
const libConnectors_1 = require("./libConnectors");
// ------------------------------------------------------------------------------------------------------------------ //
/**
* Returns a URL with a query parameter appended
*
* @param url URL to append to
* @param parameter Query parameter to append, prefixed with "?" or "&" as appropriate to what url already has
* @returns New URL combining url and parameter
*/
function appendQueryParam(url, parameter) {
return url + (url.indexOf("?") === -1 ? "?" : "&") + parameter;
}
exports.appendQueryParam = appendQueryParam;
/**
* Extracts JSON from a Blob.
*
* @param blob Blob to use as source
* @returns A promise that will resolve with JSON or null
*/
function blobToJson(blob) {
return new Promise((resolve) => {
blobToText(blob).then((blobContents) => {
try {
resolve(JSON.parse(blobContents));
}
catch (err) {
resolve(null);
}
}, () => resolve(null));
});
}
exports.blobToJson = blobToJson;
/**
* Converts a Blob to a File.
*
* @param blob Blob to use as source
* @param filename Name to use for file
* @param mimeType MIME type to override blob's MIME type
* @returns File created out of Blob and filename
*/
function blobToFile(blob, filename, mimeType) {
return new File([blob], filename ? filename : "", {
type: mimeType ?? blob.type, // Blobs default to type=""
});
}
exports.blobToFile = blobToFile;
/**
* Extracts text from a Blob.
*
* @param blob Blob to use as source
* @returns A promise that will resolve with text read from blob
*/
function blobToText(blob) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function (evt) {
// Disable needed because Node requires cast
const blobContents = evt.target.result;
resolve(blobContents ? blobContents : ""); // not handling ArrayContents variant
};
reader.readAsText(blob);
});
}
exports.blobToText = blobToText;
/**
* Checks that a URL path ends with a slash.
*
* @param url URL to check
* @returns URL, appended with slash if missing
*/
function checkUrlPathTermination(url) {
return url ? (url.endsWith("/") ? url : url + "/") : url;
}
exports.checkUrlPathTermination = checkUrlPathTermination;
/**
* Converts a hub-style item into a solutions-style item, the difference being handling of resources.
*
* @param hubModel Hub-style item
* @return solutions-style item
*/
function convertIModel(hubModel) {
const item = {
...hubModel,
};
item.resources = hubModel?.resources ? Object.values(hubModel.resources) : [];
return item;
}
exports.convertIModel = convertIModel;
/**
* Creates a random 32-character alphanumeric string.
*
* @returns A lowercase 32-char alphanumeric string
* @internal
*/
function createLongId() {
// createId gets a random number, converts it to base 36 representation, then grabs chars 2-8
return (0, hub_common_1.createId)("") + (0, hub_common_1.createId)("") + (0, hub_common_1.createId)("") + (0, hub_common_1.createId)("");
}
exports.createLongId = createLongId;
/**
* Creates a random 8-character alphanumeric string that begins with an alphabetic character.
*
* @returns An alphanumeric string in the range [a0000000..zzzzzzzz]
*/
function createShortId() {
// Return a random number, but beginning with an alphabetic character so that it can be used as a valid
// dotable property name. Used for unique identifiers that do not require the rigor of a full UUID -
// i.e. node ids, process ids, etc.
const min = 0.2777777777777778; // 0.a in base 36
const max = 0.9999999999996456; // 0.zzzzzzzz in base 36
return (_getRandomNumberInRange(min, max).toString(36) + "0000000").substr(2, 8);
}
exports.createShortId = createShortId;
/**
* Copies an input list removing duplicates.
*
* @param input List to be deduped
* @returns Deduped list; order of items in input is not maintained
*/
function dedupe(input = []) {
if (input.length === 0) {
return [];
}
const dedupedList = new Set(input);
const output = [];
dedupedList.forEach((value) => output.push(value));
return output;
}
exports.dedupe = dedupe;
/**
* Performs an asynchronous delay.
*
* @param ms Milliseconds to delay
* @returns Promise when delay is complete
*/
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
exports.delay = delay;
/**
* Flags a failure to create an item from a template.
*
* @param itemType The AGO item type
* @param id Item id to include in response
* @returns Empty creation response
*/
function generateEmptyCreationResponse(itemType, id = "") {
return {
item: null,
id,
type: itemType,
postProcess: false,
};
}
exports.generateEmptyCreationResponse = generateEmptyCreationResponse;
/**
* Generates a version 4 2-bit variant GUID.
*
* @returns A GUID
* @see {@link https://en.wikipedia.org/wiki/Universally_unique_identifier}, section "Version 4 (random)"
*/
function generateGUID() {
/** @license
* Copyright 2013 Steve Fenton
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*/
try {
return crypto.randomUUID().replace(/-/g, "");
}
catch {
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
// eslint-disable-next-line no-bitwise
const r = (Math.random() * 16) | 0;
// eslint-disable-next-line no-bitwise
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}
exports.generateGUID = generateGUID;
/**
* Returns a regular expression matching a global search for a 32-character AGO-style id.
*
* @returns Regular expression
*/
function getAgoIdRegEx() {
return /\b([0-9A-Fa-f]){32}\b/g;
}
exports.getAgoIdRegEx = getAgoIdRegEx;
/**
* Returns a regular expression matching a global search for a 32-character AGO-style id as a Solution template variable.
*
* @returns Regular expression
*/
function getAgoIdTemplateRegEx() {
return /{{\b([0-9A-Fa-f]){32}\b}}/g;
}
exports.getAgoIdTemplateRegEx = getAgoIdTemplateRegEx;
/**
* Returns a regular expression matching a global search for a word such as an AGO-style id.
*
* @param word Word to search for, bounded by regular expression word boundaries (\b)
* @returns Regular expression
*/
function getSpecifiedWordRegEx(word) {
return new RegExp(`\\b${word}\\b`, "g");
}
exports.getSpecifiedWordRegEx = getSpecifiedWordRegEx;
/**
* Extracts templated ids from a block of text.
*
* @param text Text to scan for the pattern "{{[0-9A-F]{32}.itemId}}"
* @returns List of ids found in the text; braces and ".itemId" are removed; returns empty list if no matches
*/
function getTemplatedIds(text) {
const idTest = /{{[0-9A-F]{32}.itemId}}/gi;
let ids = [];
const matches = text.match(idTest);
if (matches) {
ids = matches.map((id) => id.replace("{{", "").replace(".itemId}}", ""));
}
return ids;
}
exports.getTemplatedIds = getTemplatedIds;
/**
* Converts JSON to a Blob.
*
* @param json JSON to use as source
* @returns A blob from the source JSON
*/
function jsonToBlob(json) {
const uint8array = new TextEncoder().encode(JSON.stringify(json));
const blobOptions = { type: "application/octet-stream" };
return new Blob([uint8array], blobOptions);
}
exports.jsonToBlob = jsonToBlob;
/**
* Converts JSON to a File.
*
* @param json JSON to use as source
* @param filename Name to use for file
* @param mimeType MIME type to override blob's MIME type
* @returns File created out of JSON and filename
*/
function jsonToFile(json, filename, mimeType = "application/json") {
return blobToFile(jsonToBlob(json), filename, mimeType);
}
exports.jsonToFile = jsonToFile;
/**
* Makes a unique copy of JSON by stringifying and parsing.
*
* @param json JSON to use as source
* @returns A JSON object from the source JSON
*/
function jsonToJson(json) {
return JSON.parse(JSON.stringify(json));
}
exports.jsonToJson = jsonToJson;
/**
* Saves a blob to a file.
*
* @param filename Name to give file
* @param blob Blob to save
* @returns Promise resolving when operation is complete
*/
// Function is only used for live testing, so excluding it from coverage for now
/* istanbul ignore next */
function saveBlobAsFile(filename, blob) {
return new Promise((resolve) => {
const dataUrl = URL.createObjectURL(blob);
const linkElement = document.createElement("a");
linkElement.setAttribute("href", dataUrl);
linkElement.setAttribute("download", filename);
linkElement.style.display = "none";
document.body.appendChild(linkElement);
linkElement.click();
document.body.removeChild(linkElement);
setTimeout(() => {
URL.revokeObjectURL(dataUrl);
resolve(null);
}, 500);
});
}
exports.saveBlobAsFile = saveBlobAsFile;
/**
* Makes a deep clone, including arrays but not functions.
*
* @param obj Object to be cloned
* @returns Clone of obj
* @example
* ```js
* import { cloneObject } from "utils/object-helpers";
* const original = { foo: "bar" }
* const copy = cloneObject(original)
* copy.foo // "bar"
* copy === original // false
* ```
*/
function cloneObject(obj) {
let clone = {};
// first check array
if (Array.isArray(obj)) {
clone = obj.map(cloneObject);
}
else if (typeof obj === "object") {
if (obj instanceof File) {
const fileOptions = obj.type ? { type: obj.type } : undefined;
clone = new File([obj], obj.name, fileOptions);
}
else {
for (const i in obj) {
if (obj[i] != null && typeof obj[i] === "object") {
clone[i] = cloneObject(obj[i]);
}
else {
clone[i] = obj[i];
}
}
}
}
else {
clone = obj;
}
return clone;
}
exports.cloneObject = cloneObject;
/**
* Compares two JSON objects using JSON.stringify.
*
* @param json1 First object
* @param json2 Second object
* @returns True if objects are the same
*/
function compareJSON(json1, json2) {
return JSON.stringify(json1) === JSON.stringify(json2);
}
exports.compareJSON = compareJSON;
/**
* Compares two JSON objects using JSON.stringify, converting empty strings to nulls.
*
* @param json1 First object
* @param json2 Second object
* @returns True if objects are the same
*/
function compareJSONNoEmptyStrings(json1, json2) {
const jsonStr1 = JSON.stringify(json1).replace(/":""/g, '":null');
const jsonStr2 = JSON.stringify(json2).replace(/":""/g, '":null');
return jsonStr1 === jsonStr2;
}
exports.compareJSONNoEmptyStrings = compareJSONNoEmptyStrings;
/**
* Compares two JSON objects property by property and reports each mismatch.
*
* @param json1 First object
* @param json2 Second object
* @returns A list of mismatch report strings
*/
function compareJSONProperties(json1, json2) {
let mismatches = [];
const type1 = _typeof_null(json1);
const type2 = _typeof_null(json2);
if (type1 !== type2) {
// Ignore "undefined" vs. "null" and vice versa
/* istanbul ignore else */
if ((type1 !== "undefined" && type1 !== "null") || (type2 !== "null" && type2 !== "undefined")) {
mismatches.push("Type difference: " + type1 + " vs. " + type2);
}
}
else {
if (json1 !== json2) {
switch (type1) {
case "boolean":
mismatches.push("Value difference: " + json1 + " vs. " + json2);
break;
case "number":
mismatches.push("Value difference: " + json1 + " vs. " + json2);
break;
case "string":
mismatches.push('String difference: "' + json1 + '" vs. "' + json2 + '"');
break;
case "object":
const keys1 = Object.keys(json1);
const keys2 = Object.keys(json2);
if (keys1.length !== keys2.length || JSON.stringify(keys1) !== JSON.stringify(keys2)) {
if (Array.isArray(json1) && Array.isArray(json2)) {
mismatches.push("Array length difference: [" + keys1.length + "] vs. [" + keys2.length + "]");
}
else {
mismatches.push("Props difference: " + JSON.stringify(keys1) + " vs. " + JSON.stringify(keys2));
}
}
else {
for (let k = 0; k < keys1.length; ++k) {
const submismatches = compareJSONProperties(json1[keys1[k]], json2[keys2[k]]);
if (submismatches.length > 0) {
mismatches = mismatches.concat(submismatches);
}
}
}
break;
}
}
}
return mismatches;
}
exports.compareJSONProperties = compareJSONProperties;
/**
* Sanitizes JSON and echoes changes to console.
*
* @param json JSON to sanitize
* @param sanitizer Instance of Sanitizer class
* @returns Sanitized version of `json`
* @see https://github.com/esri/arcgis-html-sanitizer#sanitize-json
*/
function sanitizeJSONAndReportChanges(json, sanitizer) {
const sanitizedJSON = (0, libConnectors_1.sanitizeJSON)(json, sanitizer);
const mismatches = compareJSONProperties(json, sanitizedJSON);
if (mismatches.length > 0) {
console.warn("Changed " + mismatches.length + (mismatches.length === 1 ? " property" : " properties"));
mismatches.forEach((mismatch) => console.warn(" " + mismatch));
}
return sanitizedJSON;
}
exports.sanitizeJSONAndReportChanges = sanitizeJSONAndReportChanges;
function deleteItemProps(itemTemplate) {
const propsToRetain = [
"accessInformation",
"appCategories",
"banner",
"categories",
"culture",
"description",
"documentation",
"extent",
"groupDesignations",
"industries",
"languages",
"licenseInfo",
"listed",
"name",
"properties",
"proxyFilter",
"screenshots",
"size",
"snippet",
"spatialReference",
"tags",
"title",
"type",
"typeKeywords",
"url",
];
const propsToDelete = Object.keys(itemTemplate).filter((k) => propsToRetain.indexOf(k) < 0);
deleteProps(itemTemplate, propsToDelete);
return itemTemplate;
}
exports.deleteItemProps = deleteItemProps;
/**
* Deletes a property from an object.
*
* @param obj Object with property to delete
* @param path Path into an object to property, e.g., "data.values.webmap", where "data" is a top-level property
* in obj
*/
function deleteProp(obj, path) {
const pathParts = path.split(".");
if (Array.isArray(obj)) {
obj.forEach((child) => deleteProp(child, path));
}
else {
const subpath = pathParts.slice(1).join(".");
if (typeof obj[pathParts[0]] !== "undefined") {
if (pathParts.length === 1) {
delete obj[path];
}
else {
deleteProp(obj[pathParts[0]], subpath);
}
}
}
}
exports.deleteProp = deleteProp;
/**
* Deletes properties from an object.
*
* @param obj Object with properties to delete
* @param props Array of properties on object that should be deleted
*/
function deleteProps(obj, props) {
props.forEach((prop) => {
deleteProp(obj, prop);
});
}
exports.deleteProps = deleteProps;
/**
* Creates an AGO-style JSON failure response with success property.
*
* @param e Optional error information
* @returns JSON structure with property success set to false and optionally including `e`
*/
function fail(e) {
if (e) {
return { success: false, error: e.response?.error || e.error || e };
}
else {
return { success: false };
}
}
exports.fail = fail;
/**
* Creates an AGO-style JSON failure response with success property and extended with ids list.
*
* @param ids List of ids
* @param e Optional error information
* @returns JSON structure with property success set to false and optionally including `e`
*/
function failWithIds(itemIds, e) {
if (e) {
return { success: false, itemIds, error: e.error || e };
}
else {
return { success: false, itemIds };
}
}
exports.failWithIds = failWithIds;
/**
* Extracts subgroup ids from item tags
*
* @param tags Tags in an item
* @returns List of subgroup ids; subgroups are identified using tags that begin with "group." and end with a group id,
* e.g., "group.8d515625ee9f49d7b4f6c6cb2a389151"; non-matching tags are ignored
*/
function getSubgroupIds(tags) {
if (tags) {
const containedGroupPrefix = "group.";
return tags
.filter((tag) => tag.startsWith(containedGroupPrefix))
.map((tag) => tag.substring(containedGroupPrefix.length));
}
else {
return [];
}
}
exports.getSubgroupIds = getSubgroupIds;
/**
* Extracts the ids from a string
*
* @param v String to examine
* @returns List of id strings found
* @example
* get id from
* bad3483e025c47338d43df308c117308
* {bad3483e025c47338d43df308c117308
* =bad3483e025c47338d43df308c117308
* do not get id from
* http: *something/name_bad3483e025c47338d43df308c117308
* {{bad3483e025c47338d43df308c117308.itemId}}
* bad3483e025c47338d43df308c117308bad3483e025c47338d43df308c117308
*/
function getIDs(v) {
// lookbehind is not supported in safari
// cannot use /(?<!_)(?<!{{)\b[0-9A-F]{32}/gi
// use groups and filter out the ids that start with {{
return regExTest(v, /({*)(\b[0-9A-F]{32}\b)/gi).reduce(function (acc, _v) {
/* istanbul ignore else */
if (_v.indexOf("{{") < 0) {
acc.push(_v.replace("{", ""));
}
return acc;
}, []);
}
exports.getIDs = getIDs;
/**
* Gets a property out of a deeply nested object.
* Does not handle anything but nested object graph
*
* @param obj Object to retrieve value from
* @param path Path into an object, e.g., "data.values.webmap", where "data" is a top-level property
* in obj
* @returns Value at end of path
*/
function getProp(obj, path) {
return path.split(".").reduce(function (prev, curr) {
/* istanbul ignore next no need to test undefined scenario */
return prev ? prev[curr] : undefined;
}, obj);
}
exports.getProp = getProp;
/**
* Returns an array of values from an object based on an array of property paths.
*
* @param obj Object to retrieve values from
* @param props Array of paths into the object e.g., "data.values.webmap", where "data" is a top-level property
* @returns Array of the values plucked from the object; only defined values are returned
*/
function getProps(obj, props) {
return props.reduce((a, p) => {
const v = getProp(obj, p);
if (v) {
a.push(v);
}
return a;
}, []);
}
exports.getProps = getProps;
/**
* Get a property out of a deeply nested object
* Does not handle anything but nested object graph
*
* @param obj Object to retrieve value from
* @param path Path into an object, e.g., "data.values.webmap", where "data" is a top-level property
* in obj
* @param defaultV Optional value to use if any part of path--including final value--is undefined
* @returns Value at end of path
*/
function getPropWithDefault(obj, path, defaultV) {
const value = path.split(".").reduce(function (prev, curr) {
/* istanbul ignore next no need to test undefined scenario */
return prev ? prev[curr] : undefined;
}, obj);
if (typeof value === "undefined") {
return defaultV;
}
else {
return value;
}
}
exports.getPropWithDefault = getPropWithDefault;
/**
* Updates a list of the items dependencies if more are found in the
* provided value.
*
* @param v a string value to check for ids
* @param deps a list of the items dependencies
*/
function idTest(v, deps) {
const ids = getIDs(v);
ids.forEach((id) => {
/* istanbul ignore else */
if (deps.indexOf(id) === -1) {
deps.push(id);
}
});
}
exports.idTest = idTest;
/**
* Sets a deeply nested property of an object.
* Creates the full path if it does not exist.
*
* @param obj Object to set value of
* @param path Path into an object, e.g., "data.values.webmap", where "data" is a top-level property in obj
* @param value The value to set at the end of the path
*/
function setCreateProp(obj, path, value) {
const pathParts = path.split(".");
pathParts.reduce((a, b, c) => {
if (c === pathParts.length - 1) {
a[b] = value;
return value;
}
else {
if (!a[b]) {
a[b] = {};
}
return a[b];
}
}, obj);
}
exports.setCreateProp = setCreateProp;
/**
* Sets a deeply nested property of an object.
* Does nothing if the full path does not exist.
*
* @param obj Object to set value of
* @param path Path into an object, e.g., "data.values.webmap", where "data" is a top-level property in obj
* @param value The value to set at the end of the path
*/
function setProp(obj, path, value) {
if (getProp(obj, path)) {
const pathParts = path.split(".");
pathParts.reduce((a, b, c) => {
if (c === pathParts.length - 1) {
a[b] = value;
return value;
}
else {
return a[b];
}
}, obj);
}
}
exports.setProp = setProp;
/**
* Creates a timestamp string using the current UTC date and time.
*
* @returns Timestamp formatted as YYYYMMDD_hhmm_ssmmm, with month one-based and all values padded with zeroes on the
* left as needed (`ssmmm` stands for seconds from 0..59 and milliseconds from 0..999)
* @private
*/
function getUTCTimestamp() {
const now = new Date();
return (_padPositiveNum(now.getUTCFullYear(), 4) +
_padPositiveNum(now.getUTCMonth() + 1, 2) +
_padPositiveNum(now.getUTCDate(), 2) +
"_" +
_padPositiveNum(now.getUTCHours(), 2) +
_padPositiveNum(now.getUTCMinutes(), 2) +
"_" +
_padPositiveNum(now.getUTCSeconds(), 2) +
_padPositiveNum(now.getUTCMilliseconds(), 3));
}
exports.getUTCTimestamp = getUTCTimestamp;
/**
* Tests if an object's `item.typeKeywords` or `typeKeywords` properties has any of a set of keywords.
*
* @param jsonObj Object to test
* @param keywords List of keywords to look for in jsonObj
* @returns Boolean indicating result
*/
function hasAnyKeyword(jsonObj, keywords) {
const typeKeywords = getProp(jsonObj, "item.typeKeywords") || jsonObj.typeKeywords || [];
return keywords.reduce((a, kw) => {
if (!a) {
a = typeKeywords.includes(kw);
}
return a;
}, false);
}
exports.hasAnyKeyword = hasAnyKeyword;
/**
* Tests if an object's `item.typeKeywords` or `typeKeywords` properties has a specific keyword.
*
* @param jsonObj Object to test
* @param keyword Keyword to look for in jsonObj
* @returns Boolean indicating result
*/
function hasTypeKeyword(jsonObj, keyword) {
const typeKeywords = getProp(jsonObj, "item.typeKeywords") || jsonObj.typeKeywords || [];
return typeKeywords.includes(keyword);
}
exports.hasTypeKeyword = hasTypeKeyword;
/**
* Will return the provided title if it does not exist as a property
* in one of the objects at the defined path. Otherwise the title will
* have a numerical value attached.
*
* @param title The root title to test
* @param templateDictionary Hash of the facts
* @param path to the objects to evaluate for potantial name clashes
* @returns string The unique title to use
*/
function getUniqueTitle(title, templateDictionary, path) {
title = title ? title.trim() : "_";
const objs = getProp(templateDictionary, path) || [];
const titles = objs.map((obj) => {
return obj.title;
});
let newTitle = title;
let i = 0;
while (titles.indexOf(newTitle) > -1) {
i++;
newTitle = title + " " + i;
}
return newTitle;
}
exports.getUniqueTitle = getUniqueTitle;
/**
* Performs string replacement on every string in an object.
*
* @param obj Object to scan and to modify
* @param pattern Search pattern in each string
* @param replacement Replacement for matches to search pattern
* @returns Modified obj is returned
*/
function globalStringReplace(obj, pattern, replacement) {
if (obj) {
Object.keys(obj).forEach((prop) => {
const propObj = obj[prop];
if (propObj) {
/* istanbul ignore else */
if (typeof propObj === "object") {
globalStringReplace(propObj, pattern, replacement);
}
else if (typeof propObj === "string") {
obj[prop] = obj[prop].replace(pattern, replacement);
}
}
});
}
return obj;
}
exports.globalStringReplace = globalStringReplace;
/**
* Tests if an array of DatasourceInfos has a given item and layer id already.
*
* @param datasourceInfos Array of DatasourceInfos to evaluate
* @param itemId The items id to check for
* @param layerId The layers id to check for
* @returns Boolean indicating result
*/
function hasDatasource(datasourceInfos, itemId, layerId) {
return datasourceInfos.some((ds) => ds.itemId === itemId && ds.layerId === layerId);
}
exports.hasDatasource = hasDatasource;
/**
* remove templatization from item id to compare
*
* @example
* \{\{934a9ef8efa7448fa8ddf7b13cef0240.itemId\}\}
* returns 934a9ef8efa7448fa8ddf7b13cef0240
*/
function cleanItemId(id) {
return id ? id.replace("{{", "").replace(".itemId}}", "") : id;
}
exports.cleanItemId = cleanItemId;
/**
* remove templatization from layer based item id to compare
*
* @example
* \{\{934a9ef8efa7448fa8ddf7b13cef0240.layer0.itemId\}\}
* returns 934a9ef8efa7448fa8ddf7b13cef0240
*/
function cleanLayerBasedItemId(id) {
return id ? id.replace("{{", "").replace(/([.]layer([0-9]|[1-9][0-9])[.](item|layer)Id)[}]{2}/, "") : id;
}
exports.cleanLayerBasedItemId = cleanLayerBasedItemId;
/**
* remove templatization from layer id to compare
*
* @example
* \{\{934a9ef8efa7448fa8ddf7b13cef0240.layer0.layerId\}\}
* returns 0
*/
function cleanLayerId(id) {
return id?.toString()
? parseInt(id
.toString()
.replace(/[{]{2}.{32}[.]layer/, "")
.replace(/[.]layerId[}]{2}/, ""), 10)
: id;
}
exports.cleanLayerId = cleanLayerId;
/**
* Get template from list of templates by ID
*
* @param templates Array of item templates to search
* @param id of template we are searching for
*
* @returns Template associated with the user provided id argument
*/
function getTemplateById(templates, id) {
let template;
templates.some((_template) => {
if (_template.itemId === id) {
template = _template;
return true;
}
return false;
});
return template;
}
exports.getTemplateById = getTemplateById;
/**
* Evaluates a value with a regular expression
*
* @param v a string value to test with the expression
* @param ex the regular expresion to test with
* @returns an array of matches
*/
function regExTest(v, ex) {
return v && ex.test(v) ? v.match(ex) : [];
}
exports.regExTest = regExTest;
/**
* Removes duplicates from a list of strings.
*
* @param list List to be de-duped
* @returns List of unique strings
*/
function uniqueStringList(list) {
return list.filter(hub_common_1.unique);
}
exports.uniqueStringList = uniqueStringList;
// ------------------------------------------------------------------------------------------------------------------ //
/**
* Creates a random number between two values.
*
* @param min Inclusive minimum desired value
* @param max Non-inclusive maximum desired value
* @returns Random number in the range [min, max)
*/
function _getRandomNumberInRange(min, max) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_number_between_two_values
// © 2006 IvanWills
// MIT license https://opensource.org/licenses/mit-license.php
return Math.random() * (max - min) + min;
}
exports._getRandomNumberInRange = _getRandomNumberInRange;
/**
* Pads the string representation of a number to a minimum width. Requires modern browser.
*
* @param n Number to pad
* @param totalSize Desired *minimum* width of number after padding with zeroes
* @returns Number converted to string and padded on the left as needed
* @private
*/
function _padPositiveNum(n, totalSize) {
let numStr = n.toString();
const numPads = totalSize - numStr.length;
if (numPads > 0) {
numStr = "0".repeat(numPads) + numStr; // TODO IE11 does not support repeat()
}
return numStr;
}
exports._padPositiveNum = _padPositiveNum;
/**
* Implements rejected ECMA proposal to change `typeof null` from "object" to "null".
*
* @param value Value whose type is sought
* @returns "null" if `value` is null; `typeof value` otherwise
* @see https://web.archive.org/web/20160331031419/http://wiki.ecmascript.org:80/doku.php?id=harmony:typeof_null
* @private
*/
function _typeof_null(value) {
return value === null ? "null" : typeof value;
}
//# sourceMappingURL=generalHelpers.js.map