@dbp-toolkit/common
Version:
You can provide attributes (e.g. `global-name`) for components inside the provider:
392 lines (348 loc) • 10.4 kB
JavaScript
/**
* Parses the base url from an url
*
* @param url
* @returns {string}
*/
export const parseBaseUrl = (url) => {
const pathArray = url.split('/');
const protocol = pathArray[0];
const host = pathArray[2];
return protocol + '//' + host;
};
/**
* Converts a string list to a data array for Select2
*
* @param list
* @returns {Array}
*/
export const stringListToSelect2DataArray = (list) => {
let data = [];
list.forEach((item) => {
data.push({id: item, text: item});
});
return data;
};
/**
* Converts a key-value object to a data array for Select2
*
* @param keyValueObject
* @returns {Array}
*/
export const keyValueObjectToSelect2DataArray = (keyValueObject) => {
let data = [];
Object.keys(keyValueObject).forEach((key) => {
data.push({id: key, text: keyValueObject[key]});
});
return data;
};
/**
* Does generic Base64 Encoding with support for 16-bit encoded strings
*
* @see https://www.base64encoder.io/javascript/
* @param str
* @returns {string}
*/
export const base64EncodeUnicode = (str) => {
// First we escape the string using encodeURIComponent to get the UTF-8 encoding of the characters,
// then we convert the percent encodings into raw bytes, and finally feed it to btoa() function.
const utf8Bytes = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
return String.fromCharCode('0x' + p1);
});
return btoa(utf8Bytes);
};
/**
* Like customElements.define() but tries to display an error in case the browser doesn't
* support everything we need.
*
* Returns false in case define failed, true otherwise.
*
* @returns {boolean}
*/
/**
* Same as customElements.define() but less strict.
*
* In case the same component (with the exact same implementation) is already
* defined then this will do nothing instead of erroring out.
*
* @param {string} name
* @param {Function} constructor
* @param {object} options
*/
export const defineCustomElement = (name, constructor, options) => {
// In case the constructor is already defined just do nothing
if (customElements.get(name) === constructor) {
return true;
}
customElements.define(name, constructor, options);
return true;
};
/**
* Creates a random id
*
* taken from: https://stackoverflow.com/a/1349426/1581487
*
* @param length
* @returns {string}
*/
export const makeId = (length) => {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
/**
* Pads a number with a 0, so it has two digits
*
* @param n
* @returns {string}
*/
export const pad10 = (n) => {
return n < 10 ? '0' + n : n;
};
/**
* Converts a date object or string to a local iso datetime with stripped seconds and timezone for the datetime-local input
*
* @param date
* @returns {string}
*/
export const dateToStrippedIsoDT = (date) => {
if (!(date instanceof Date)) {
date = new Date(date);
}
return `${date.getFullYear()}-${pad10(date.getMonth() + 1)}-${pad10(date.getDate())}T${pad10(
date.getHours(),
)}:${pad10(date.getMinutes())}`;
};
/**
* Converts a date object or string to a local date string the date input
*
* @param date
* @returns {string}
*/
export const dateToInputDateString = (date) => {
if (!(date instanceof Date)) {
date = new Date(date);
}
return `${date.getFullYear()}-${pad10(date.getMonth() + 1)}-${pad10(date.getDate())}`;
};
/**
* Converts a date object or string to a local time string the time input
*
* @param date
* @returns {string}
*/
export const dateToInputTimeString = (date) => {
if (!(date instanceof Date)) {
date = new Date(date);
}
return `${pad10(date.getHours())}:${pad10(date.getMinutes())}`;
};
/**
* Get an absolute URL for shared assets (hashed assets).
*
* @param {string} path The relative path to the shared asset
* @param {object} [options] Optional parameters
* @param {string} [options.metaUrl] Custom meta URL for testing
* @returns {string} The absolute URL to the shared asset
*/
export const getAbsoluteURL = (path, options = {}) => {
const baseUrl = getBaseURL(options);
return new URL(path, baseUrl).href;
};
/**
* Get an absolute URL for local assets (namespaced by package).
*
* @param {string} pkgName The package which provides the asset
* @param {string} path The relative path within the package
* @param {object} [options] Optional parameters
* @param {string} [options.metaUrl] Custom meta URL for testing
* @returns {string} The absolute URL to the local asset
*/
export const getAssetURL = (pkgName, path, options = {}) => {
if (path === undefined) {
console.warn('getAssetURL called without package name, use getAbsoluteURL instead');
return getAbsoluteURL(pkgName, options);
}
const fullPath = 'local/' + pkgName + '/' + path;
const baseUrl = getBaseURL(options);
return new URL(fullPath, baseUrl).href;
};
/**
* Get the base URL for asset resolution.
*
* @param {object} [options] Optional parameters
* @param {string} [options.metaUrl] Custom meta URL for testing
* @returns {URL} The base URL for asset resolution
*/
const getBaseURL = (options = {}) => {
let baseUrl = new URL(options.metaUrl !== undefined ? options.metaUrl : import.meta.url);
// If we are under "/shared/" assume we are called from a chunk
// and need to go up one level
if (baseUrl.pathname.split('/').slice(-2)[0] === 'shared') {
baseUrl = new URL('..', baseUrl);
}
return baseUrl;
};
/**
* Poll <fn> every <interval> ms until <timeout> ms
*
* @param fn
* @param timeout
* @param interval
*/
export const pollFunc = (fn, timeout, interval) => {
var startTime = new Date().getTime();
interval = interval || 1000;
(function p() {
// don't retry if we took longer than timeout ms
if (new Date().getTime() - startTime > timeout) {
return;
}
// retry until fn() returns true
if (!fn()) {
setTimeout(p, interval);
}
})();
};
/**
* Doing an async foreach for non-linear integer keys
*
* @param array
* @param callback
* @returns {Promise<void>}
*/
export async function asyncObjectForEach(array, callback) {
const keys = Object.keys(array);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
await callback(array[key], key, array);
}
}
/**
* Doing an async foreach for non-linear integer keys with a copy of the array
*
* @param array
* @param callback
* @returns {Promise<void>}
*/
export async function asyncCopyObjectForEach(array, callback) {
const arrayCopy = {...array};
const keys = Object.keys(arrayCopy);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
await callback(arrayCopy[key], key, arrayCopy);
}
}
/**
* Doing an async foreach for linear integer keys
*
* @param array
* @param callback
* @returns {Promise<void>}
*/
export async function asyncArrayForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
/**
* Attempts to determine the mime-type of a file or blob
*
* @param file
* @returns {Promise<string>}
*/
export async function getMimeTypeOfFile(file) {
const getMimeType = (signature) => {
switch (signature) {
case '89504E47':
return 'image/png';
case '47494638':
return 'image/gif';
case '25504446':
return 'application/pdf';
case 'FFD8FFDB':
case 'FFD8FFE0':
case 'FFD8FFE1':
return 'image/jpeg';
case '504B0304':
return 'application/zip';
default:
return 'Unknown filetype';
}
};
return await new Promise((resolve) => {
let fileReader = new FileReader();
fileReader.onloadend = function (evt) {
if (evt.target.readyState === FileReader.DONE) {
const uint = new Uint8Array(evt.target.result);
let bytes = [];
uint.forEach((byte) => {
bytes.push(byte.toString(16));
});
const hex = bytes.join('').toUpperCase();
const mimeType = getMimeType(hex);
resolve(mimeType);
}
};
fileReader.readAsArrayBuffer(file.slice(0, 4));
});
}
/**
* Get the basename of a filename
*
* @param str
* @returns {string}
*/
export const getBaseName = (str) => {
let base = String(str).substring(str.lastIndexOf('/') + 1);
if (base.lastIndexOf('.') !== -1) {
base = base.substring(0, base.lastIndexOf('.'));
}
return base;
};
/**
* Get the file extension of a filename
*
* @param str
* @returns {string}
*/
export const getFileExtension = (str) => {
return str.split('.').pop();
};
/**
* Queries for "selector" in "root" in the slot html
*
* @param root
* @param selector
* @returns {*[]}
*/
export const querySlotted = (root, selector) => {
let slots = root.querySelectorAll('slot');
let matched = [];
slots.forEach((slot) => {
matched = matched.concat(
slot.assignedElements().filter((el) => {
return el.matches(selector);
}),
);
});
return matched;
};
/**
* Generates a random UUID (Universally Unique Identifier) version 4.
* Same as crypto.randomUUID() but works in unsecure contexts (http).
*
* @returns {string} A randomly generated UUID v4 string.
*/
export function createUUID() {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10
const hex = [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
}