@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
247 lines (245 loc) • 8.43 kB
JavaScript
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { camelize } from './string';
import queryString from 'query-string';
export function pluckProps(source, ...props) {
let omitNull = false;
const object = {};
if (!source) {
return object;
}
if (typeof props[0] === 'boolean') {
omitNull = true;
props.shift();
}
props.forEach((_prop_) => {
const prop = String(_prop_);
const propName = prop.substr(prop.lastIndexOf('.') + 1);
const value = retrieveProperty(source, prop);
if (nnou(value) || !omitNull) {
object[propName] = value;
}
});
return object;
}
export function reversePluckProps(source, ...props) {
const object = {};
if (!source) {
return object;
}
for (let key in source) {
if (!props.includes(key) && source.hasOwnProperty(key)) {
object[key] = source[key];
}
}
return object;
}
export function camelizeProps(obj) {
return Object.entries(obj).reduce((camelized, [key, value]) => {
camelized[camelize(key)] = value;
return camelized;
}, {});
}
export function createLookupTable(list, idProp = 'id') {
const table = {};
list.forEach((item) => {
table[retrieveProperty(item, idProp)] = item;
});
return table;
}
export function flattenHierarchical(root, childrenProp = 'children') {
return (Array.isArray(root) ? root : [root]).flatMap((node) =>
Boolean(node) ? [node, ...flattenHierarchical(node[childrenProp] ?? [], childrenProp)] : null
);
}
export function hierarchicalToLookupTable(root, childrenProp = 'children', idProp = 'id') {
return createLookupTable(normalizeProp(flattenHierarchical(root, childrenProp), idProp, childrenProp), idProp);
}
// TODO: Types here could be better.
export function normalizeProp(list, idProp = 'id', normalizeTargetProp = 'children') {
return list.map((item) => ({
...item,
[normalizeTargetProp]: item[normalizeTargetProp]?.map((child) => child[idProp])
}));
}
export function retrieveProperty(object, prop) {
return object == null ? null : !prop ? object : prop.split('.').reduce((value, prop) => value[prop], object);
}
export function deleteProperty(object, prop) {
delete object[prop];
return object;
}
export function setProperty(object, prop, value) {
if (object) {
const props = prop.split('.');
const propToSet = props.pop();
let target = retrieveProperty(object, props.join('.'));
if (!target) {
setProperty(object, props.join('.'), {});
target = retrieveProperty(object, props.join('.'));
}
target[propToSet] = value;
}
return object;
}
let UND;
// Not Null Or Undefined (nnou)
export function nnou(object) {
return object !== null && object !== UND;
}
export const notNullOrUndefined = nnou;
// Null Or Undefined (nou)
export function nou(object) {
return object === null || object === UND;
}
export const nullOrUndefined = nou;
export function createEntityState(merge = {}) {
return {
byId: null,
error: null,
isFetching: null,
...merge
};
}
export function resolveEntityCollectionFetch(collection) {
return createEntityState({
byId: createLookupTable(collection)
});
}
export function ref(ref) {
return ref.current;
}
export function isPlainObject(obj) {
return typeof obj === 'object' && obj !== null && obj.constructor === Object;
}
/** @deprecated Use extend(target, source) */
export function extendDeep(target, source) {
return extend(target, source, { deep: true });
}
/** @deprecated Use extend(target, source, { existingOnly: true }) */
export function extendDeepExistingProps(target, source) {
return extend(target, source, { existingOnly: true });
}
export function extend(target, source, options) {
options = Object.assign({ existingOnly: false, deep: true }, options);
if (!options.deep) {
return Object.assign(target, source);
}
for (let prop in source) {
if (source.hasOwnProperty(prop) && (!options.existingOnly || (options.existingOnly && prop in target))) {
if (prop in target && isPlainObject(target[prop]) && isPlainObject(source[prop])) {
extend(target[prop], source[prop]);
} else {
target[prop] = source[prop];
}
}
}
return target;
}
export function toQueryString(args, options) {
if (!args) {
return '';
}
options = { prefix: '?', ...options };
return `${options.prefix}${queryString.stringify(args, options)}`;
}
export function applyDeserializedXMLTransforms(target, options) {
const { arrays, lookupTables, renameTable } = options;
const newObject = {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
let newName = renameTable?.[prop] ?? prop;
if (arrays?.includes(newName)) {
if (typeof target[prop] === 'string') {
newObject[newName] = [];
} else if (
// @ts-ignore
target[prop]._preserve === 'true'
) {
newObject[newName] = target[prop];
delete newObject[newName]._preserve;
} else {
const keys = Object.keys(target[prop]);
const childName = keys[0];
newObject[newName] = Array.isArray(target[prop][childName])
? target[prop][childName]
: [target[prop][childName]];
newObject[newName] = newObject[newName].map((item) =>
typeof item === 'object' ? applyDeserializedXMLTransforms(item, options) : item
);
}
} else if (lookupTables?.includes(newName)) {
if (typeof target[prop] === 'string') {
newObject[newName] = {};
} else {
const keys = Object.keys(target[prop]);
const childName = keys[0];
if (Array.isArray(target[prop][childName])) {
// Assume single key as in `{ lookupTableKeyName: { childName: [{}, {}, {}] } }`
const tempArray = target[prop][childName]
.filter(Boolean)
.map((item) => applyDeserializedXMLTransforms(item, options));
newObject[newName] = createLookupTable(tempArray);
} else {
// Assume multiple keys that will be used as the index
newObject[newName] = {};
keys.forEach((key) => {
newObject[newName][key] = applyDeserializedXMLTransforms(target[prop][key], options);
});
}
}
} else {
newObject[newName] =
typeof target[prop] === 'object' ? applyDeserializedXMLTransforms(target[prop], options) : target[prop];
}
}
}
return newObject;
}
export function deepCopy(target) {
return JSON.parse(JSON.stringify(target));
}
export const foo = {};
export const fooFn = () => undefined;
export function isApiResponse(source) {
source = source ?? {};
return (
Object.prototype.hasOwnProperty.call(source, 'code') && Object.prototype.hasOwnProperty.call(source, 'message')
);
}
export function isAjaxError(source) {
source = source ?? {};
return (
Object.prototype.hasOwnProperty.call(source, 'message') &&
Object.prototype.hasOwnProperty.call(source, 'status') &&
Object.prototype.hasOwnProperty.call(source, 'name')
);
}