Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
utils.utils.js Maven / Gradle / Ivy
import React from 'react'
import {
useCallback,
useEffect,
useState } from 'react'
import {
useApolloClient } from '@apollo/client'
import reform from 'immutability-helper'
import { format, parse } from 'date-fns'
import _ from 'lodash'
import Report from 'helpers/report'
import CenteredLoader from 'components/common/CenteredLoader'
import { toRegex } from 'utils/option-utils'
import { nullOrEmpty } from '../components/form/fields/utils/values'
import Types from 'types/types'
// === misc functions === //
const urlRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/i
export const isUrl = (str) => urlRegex.test(str) // TODO rename to is*Absolute*Url?
export function addObject(condition, obj) {
return condition ? [obj] : []
}
export function addPromiseStatus(promise) {
// Don't modify any promise that has been already modified.
if (promise.isFulfilled) return promise;
// Set initial state
var isPending = true;
var isRejected = false;
var isFulfilled = false;
// Observe the promise, saving the fulfillment in a closure scope.
var result = promise.then(
function(v) {
isFulfilled = true;
isPending = false;
return v;
},
function(e) {
isRejected = true;
isPending = false;
throw e;
}
);
result.isFulfilled = function() { return isFulfilled; };
result.isPending = function() { return isPending; };
result.isRejected = function() { return isRejected; };
return result;
}
export function env(name) {
const key = 'REACT_APP_' + name
return (global._env && global._env[key]) || process.env[key]
}
export function isString(obj) {
return typeof obj === 'string' || obj instanceof String
}
export const setTimeoutPromise = (cb, delay) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(cb());
}, delay);
});
export function asyncSleep(delay = 0) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
export function sleep(delay = 0) {
(async () => { await asyncSleep(delay) })()
}
export function toTypeString( obj ) {
return ({}).toString.call( obj ).match(/\s(\w+)/)[1].toLowerCase();
}
export function isOfType(arg, type) {
if (typeof type !== "string")
throw new TypeError("Provide a type specifier, e.g.: 'string!'")
const simpleType = type.replace(/!/g, '').toLowerCase()
const actualType = toTypeString(arg)
return (! /.*!$/.test(type) || arg !== null) && actualType == simpleType;
}
export function assertType(arg, type, msg = "") {
if (!isOfType(arg, type)) {
const defaultMsg = "value '" + arg + "' is not of expected type '" + type + "'."
const message = msg ? msg : defaultMsg
throw new TypeError(message);
}
}
export function assertTypes( args, types ) {
args = [].slice.call( args );
for ( var i = 0; i < types.length; ++i )
assertType(args[i], types[i], i)
}
export function pruneEmpty(obj) {
return function prune(current) {
_.forOwn(current, function (value, key) {
if (_.isUndefined(value) || _.isNull(value) || _.isNaN(value) ||
(Types.isObject(value) && _.isEmpty(prune(value))) ||
(Array.isArray(value) && value.length == 0))
{
delete current[key];
}
});
// remove any leftover undefined values from the delete
// operation on an array
if (_.isArray(current)) _.pull(current, undefined);
return current
}(_.cloneDeep(obj)); // Do not modify the original object, create a clone instead
}
export function hasNonEmptyValue(obj) {
if (Types.isObject(obj))
return Object.values(obj).some(hasNonEmptyValue)
else if (Array.isArray(obj))
return obj.some(hasNonEmptyValue)
else
return !Boolean(isEmptyValue(obj))
}
export function isEmptyValue(value) {
return value === null || value === undefined || value === "" || (Array.isArray(value) && value.length === 0)
}
export function withDefault(value, fallback) {
return isEmptyValue(value) ? fallback : value
}
export function mapSet(set, f) {
var newSet = new Set();
for (var v of set.values()) newSet.add(f(v));
return newSet;
};
export function addLinesAt(lines, index, selected, defaultValue) {
if (!Array.isArray(lines))
return lines
var indexes = Array.from(selected).sort();
const selectedLines = fallback(indexes.map(index => fallback(lines[index], defaultValue)), [defaultValue])
return reform(lines, { $splice: [[index + 1, 0, ...selectedLines]] })
}
export function deleteLinesAt(lines, index, nr) {
if (!Array.isArray(lines))
return lines
return reform(lines, { $splice: [[index, nr]] })
}
export function fallback(value, fallback) {
return nullOrEmpty(value) ? fallback : value
}
// this is the opposite of renderPath. given a rpath produces the 'indices' object expected by renderPath
export function extractIndices(rpath) {
let indices = {}
if (isString(rpath)) {
const matches = rpath.matchAll(/(\w+)\[(\d+)\]/g)
for (const match of matches) {
const name = match[1]
const index = match[2]
indices[name] = parseInt(index)
}
}
return indices
}
export function renderPath(path, indices) {
return path.replace(/\$\w+/g, (matched) => {
const name = matched.substring(1)
const index = indices[name]
//console.log("renderPath(%o): name=%o, index=%o", path, name, index)
return index
});
}
/** @deprecated no longer used */
export function valuesToFormEntries(values) {
return new Array(...values).map(([key, value]) => { return { key, value } })
}
// find the first occurance of 'key' recursively, and return the value
export function findVal(object, key) {
var value;
const resolve = function(k) {
if (k === key) {
value = object[k];
return true;
}
value = findVal(object[k], key);
return value !== undefined;
}
if (Types.isObject(object))
Object.keys(Types.asObject(object)).some(resolve);
return value;
}
export function getPath(obj, path, dflt = undefined) {
if (obj == undefined)
return dflt
else if (path == "")
return obj
else if (!_.isString(path))
throw "Function getPath was provided with an invalid path: '" + path + "'"
else
return _.get(obj, path, dflt)
}
/* paginate data. starting from index 1! */
export function paginate(array, pageSize, page) {
if (Array.isArray(array))
return array.slice(page * pageSize, (page + 1) * pageSize);
else
return []
}
export function handleResults(results, notifier, callback, errorCallbacks) {
const defaultErrorCallback = (error) => {
console.error("GraphQL error: %o", error)
const report = Report.error(Report.backend, Report.code.backend.Error)
report.addToNotifier(notifier)
return report.toNotification({reload: true})
}
if (!Array.isArray(results) || results.some(result => !result.hasOwnProperty('loading'))) {
notifier.error("Function handleResults only accepts an array of graphql queries")
return null
}
const loading = results.some(result => result.loading)
if (loading)
return
const errorIndex = results.findIndex(result => Boolean(result.error))
if (errorIndex >= 0) {
const error = results[errorIndex].error
if (errorCallbacks) {
if (errorCallbacks.length > errorIndex)
return (errorCallbacks[errorIndex])(error)
else
return errorCallbacks(error)
} else
return defaultErrorCallback(error)
}
const result = results.map(result => result.data)
console.log('handleResults: data=%o', result)
return callback(result)
}
export function notifyOnError(result, notifier) {
const { loading, error } = result
if (loading)
return undefined
if (error) {
console.error("GraphQL error: %o", error)
const report = Report.error(Report.backend, Report.code.backend.Error)
report.addToNotifier(notifier)
}
}
export function handleError(result, notifier, errorCallback) {
const defaultErrorCallback = (error) => {
console.error("GraphQL error: %o", error)
const report = Report.error(Report.backend, Report.code.backend.Error)
report.addToNotifier(notifier)
return report.toNotification({reload: true})
}
console.log("error result: %o", result)
const { loading, error } = result
if (loading)
return null
if (error)
return errorCallback ? errorCallback(error) : defaultErrorCallback(error)
}
export function handleResult(result, notifier, callback, errorCallback) {
const results = Array.isArray(result) ? result : [result]
// handle loading
if (results.some(result => result.loading))
return
// handle errors
const errorResult = results.find(result => result.error)
if (errorResult){
console.log("handleError: %o", errorResult)
return handleError(errorResult, notifier, errorCallback)
}
// handle data
const datas = results.map(result => result.data)
console.log('handleResult: data=%o', datas.length == 1 ? datas[0] : datas)
if (datas.every(data => !data))
return null
return callback(datas.length == 1 ? datas[0] : datas)
}
// create a range, i.e., a list with [0,1,...,number-1]
export function range(number) {
return [...Array(number).keys()];
}
export function updateObject(target, source) {
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
if (target[prop] && typeof source[prop] === 'object') {
updateObject(target[prop], source[prop]);
}
else {
target[prop] = source[prop];
}
}
}
return target;
}
export function updateQuery(cache, query, updater) {
const data = cache.readQuery({ query })
console.log("updateStore: before=%o", data)
const newData = updater(data)
console.log("updateStore: after=%o", newData)
cache.writeQuery({ query: query, data: newData })
}
export function openInNewTab(url) {
window.open(url, '_blank', 'noopener,noreferrer');
}
const isFile = input => 'File' in window && input instanceof File;
const isBlob = input => 'Blob' in window && input instanceof Blob;
const arrayFileReducer = ([previousValues, previousFiles], value) => {
const [newValue, files] = extractFiles(value)
return [[...previousValues, newValue], previousFiles.concat(files)]
}
const objectFileReducer = ([previousEntries,previousFiles],[key, value]) => {
const [newValue, files] = extractFiles(value)
return [[...previousEntries, [key, newValue]], previousFiles.concat(files)]
}
// this function replaces values of type File (or Blob) with an id, and returns the file seperately,
// e.g. value: {attr: File {name: filename}} => [{ attr: 'filename}, [File {name: 'filename'}]]
export function extractFiles(value) {
switch (true) {
case isFile(value) || isBlob(value):
return [value.path, [value]]
case Types.isContent(value):
const content = Types.toContent(value)
return content.hasData ? [content.path, [content.file]] : [content.toContentRef(), []]
case Array.isArray(value):
return value.reduce(arrayFileReducer, [[],[]])
case Types.isObject(value):
const [entries, files] = Object.entries(value).reduce(objectFileReducer, [[],[]])
return [Object.fromEntries(entries), files]
default:
return [value,[]]
}
}
export function reformQuery(cache, query, reformer) {
const data = cache.readQuery({ query })
console.log("updateStore: before=%o", data)
if (data) {
const newData = reform(data, reformer)
console.log("updateStore: after=%o", newData)
cache.writeQuery({ query: query, data: newData })
} else {
console.warn("could not update cached graphql result for: ", JSON.stringify(query))
}
}
export function removerById(id) {
return elements => elements.filter(elem => elem.id !== id)
}
// === hooks === //
/** @deprecated no longer used */
export function useValue(ctx, path, initial) {
const [state, setState] = React.useState(initial)
ctx.setValue(path, state)
function setValue(value) {
ctx.setValue(path, value)
setState(value)
}
return [state, setValue]
}
export function useDebouncedState(initial, delay) {
const [state, setState] = React.useState(initial)
// eslint-disable-next-line
const debounce = React.useCallback(
_.debounce(setState, delay),
[delay]
)
const setDebouncedState = val => debounce(val)
const setImmediateState = val => {
debounce.cancel()
setState(val)
}
return [state, setDebouncedState, setImmediateState]
}
// a debouncer, with a shirtcircuit to skip the delay before update. E.g., press Enter to update immediately
export function useDebounce(initialValue, delay) {
const [value, setValue] = useState(initialValue)
const [debouncedValue, setDebouncedValue, setDebouncedImmediate] = useDebouncedState(initialValue, delay)
useEffect(() => {
setDebouncedValue(value)
}, [value, delay])
return [value, setValue, debouncedValue, () => { setDebouncedImmediate(value) }]
}
// see https://github.com/apollographql/react-apollo/issues/3499
export function usePromiseQuery(query) {
const client = useApolloClient()
return React.useCallback(
variables => client.query({ query, variables }),
[client, query]
)
}
// === date-related functions === //
// NOTE default JavaScript date format is ECMA-262
// TODO deels dubbel met dt-formats.js?
export const displayFormats = {
date: 'dd-MM-yyyy',
time: 'HH:mm:ss',
dateTime: 'dd-MM-yyyy HH:mm:ss',
}
export const referenceFormats = {
date: 'yyyy-MM-dd',
time: 'HH:mm:ss',
dateTime: 'yyyy-MM-ddTHH:mm:ss',
}
const timezoneConversions = {
ACDT: "+1030",
ACST: "+0930",
ADT: "-0300",
AEDT: "+1100",
AEST: "+1000",
AHDT: "-0900",
AHST: "-1000",
AST: "-0400",
AT: "-0200",
AWDT: "+0900",
AWST: "+0800",
BAT: "+0300",
BDST: "+0200",
BET: "-1100",
BST: "-0300",
BT: "+0300",
BZT2: "-0300",
CADT: "+1030",
CAST: "+0930",
CAT: "-1000",
CCT: "+0800",
CDT: "-0500",
CED: "+0200",
CET: "+0100",
CEST: "+0200",
CST: "-0600",
EAST: "+1000",
EDT: "-0400",
EED: "+0300",
EET: "+0200",
EEST: "+0300",
EST: "-0500",
FST: "+0200",
FWT: "+0100",
GMT: "GMT",
GST: "+1000",
HDT: "-0900",
HST: "-1000",
IDLE: "+1200",
IDLW: "-1200",
IST: "+0530",
IT: "+0330",
JST: "+0900",
JT: "+0700",
MDT: "-0600",
MED: "+0200",
MET: "+0100",
MEST: "+0200",
MEWT: "+0100",
MST: "-0700",
MT: "+0800",
NDT: "-0230",
NFT: "-0330",
NT: "-1100",
NST: "+0630",
NZ: "+1100",
NZST: "+1200",
NZDT: "+1300",
NZT: "+1200",
PDT: "-0700",
PST: "-0800",
ROK: "+0900",
SAD: "+1000",
SAST: "+0900",
SAT: "+0900",
SDT: "+1000",
SST: "+0200",
SWT: "+0100",
USZ3: "+0400",
USZ4: "+0500",
USZ5: "+0600",
USZ6: "+0700",
UT: "-0000",
UTC: "-0000",
UZ10: "+1100",
WAT: "-0100",
WET: "-0000",
WST: "+0800",
YDT: "-0800",
YST: "-0900",
ZP4: "+0400",
ZP5: "+0500",
ZP6: "+0600"
}
// convert a string timezone to hour shift, e.g., CET => +0100, CEST => +0200
function withoutTimezone(dateTimeString){
const reducer = (timeString, [timezone, replacement]) => timeString.replace(timezone, replacement)
return Object.entries(timezoneConversions).reduce(reducer, dateTimeString)
}
// reference string -> display string
export function convertToDate(value) {
return value instanceof Date ? value : new Date(withoutTimezone(value))
}
// date -> string
export function formatDate(value) {
const result = value === null ? '' : format(value, displayFormats.date)
return result
}
export function formatTime(value) {
return format(value, displayFormats.time)
}
export function formatDateTime(value) {
return format(value, displayFormats.date)
}
// string -> date
export function parseDate(value) {
const result = nullOrEmpty(value) ? null : parse(value, displayFormats.date, new Date())
return result
}
export function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(
() => window.localStorage.getItem(key) || initialValue
)
const setItem = (newValue) => {
setValue(newValue);
if (newValue === null)
window.localStorage.removeItem(key)
else
window.localStorage.setItem(key, newValue)
}
useEffect(() => {
const newValue = window.localStorage.getItem(key)
if (value !== newValue) {
setValue(newValue || initialValue)
}
})
const handleStorage = useCallback(
(event) => {
if (event.key === key && event.newValue !== value) {
setValue(event.newValue || initialValue)
}
},
[value]
);
useEffect(() => {
window.addEventListener('storage', handleStorage)
return () => window.removeEventListener('storage', handleStorage)
}, [handleStorage])
return [value, setItem]
}
export function filterTableData(data, filterExpression, toRows, toRow, ...toColumns) {
const regex = toRegex(filterExpression)
function matchColumn(data, toColumn) {
const column = toColumn ? toColumn(data) : data
return column === null || column === undefined ? false : regex.test(column)
}
function filterRow(data) {
const row = toRow ? toRow(data) : data
if (Array.isArray(row) && toColumns.length === 0)
return row.some(matchColumn)
else
return toColumns.some(toColumn => matchColumn(row, toColumn))
}
function filterRows(data) {
const rows = toRows ? toRows(data) : data
return rows.filter(rowData => filterRow(rowData));
}
return filterRows(data)
}
export function isEvent(e) {
return e instanceof Event || _.get(e, "nativeEvent") instanceof Event || _.get(e, "e.originalEvent") instanceof Event;
}
export function isDate(date) {
return date instanceof Date
}
export function isValidDate(date) {
return isDate(date) && new Date(date).toString() !== 'Invalid Date';
}