import { clamp, defaultTo, groupBy, identical, is, isEmpty, isNil, mergeAll, mergeDeepWith } from 'ramda';
/**
* 根据参数 param 返回一个对应的返回值
* @summary 有参回调
* @template T 参数类型
* @template K 返回值类型
* @callback FHelp~TParam
* @param {T} param 参数
* @return {K} 返回值
*/
/**
* @class
* @summary 辅助类
* @classdesc 为常用操作提供的实用函数库
* @hideconstructor
* @memberof module:utils
* @alias FHelp
* @see 关于 ramda 请查阅[文档](https://ramda.cn/docs/#)
*/
export class FHelp {
/**
* 将多个类混合到基类
*
* 基础类上存在的 `key` 不会被其他类覆盖
* @summary 混合类型
* @param {ObjectConstructor} base 基类
* @param {...ObjectConstructor} classes 混合类
* @return {ObjectConstructor} 基类
* @static
*/
static mixin(base, ...classes) {
if (base?.prototype == null) {
return base;
}
for (const cls of classes) {
if (cls?.prototype == null) continue;
//获取方法
Object.getOwnPropertyNames(cls.prototype).forEach((key) => {
if (!base.prototype[key]) {
base.prototype[key] = cls.prototype[key];
//克隆源对象键值对目标对象
//Object.defineProperty(base, key, cls.prototype[key]);
}
});
//获取属性
const ins = new cls;
const baseIns = new base;
Object.getOwnPropertyNames(ins).forEach((key) => {
if (!baseIns[key]) {
base.prototype[key] = ins[key];
//克隆源对象键值对目标对象
//Object.defineProperty(base, key, ins[key]);
}
});
}
return base;
}
/**
* 将多个对象上的属性混合到基础属性里
*
* 基础属性上存在的 `key` 不会被其他属性覆盖
* @summary 混合属性
* @param {InstanceType<Object>} base 基础属性
* @param {...InstanceType<Object>} property 其他属性
* @return {InstanceType<Object>} 基础属性
* @static
*/
static mixinProperty(base, ...property) {
if (FHelp.isNotValid(base)) {
return base;
}
for (const ins of property) {
// 使用 prototype 判断
// class.prototype != null
// ({}).prototype == null
if (base.prototype == null && ins && ins.prototype == null) {
Object.getOwnPropertyNames(ins).forEach((key) => {
if (!base[key]) {
base[key] = ins[key];
//克隆源对象键值对目标对象
//Object.defineProperty(base, key, ins[key]);
}
});
}
}
return base;
}
/**
* 判断给定参数是否有效
* @summary 参数无效
* @param {any} param
* @return {boolean} true: 无效
* @static
* @example
* FHelp.isNotValid(null); //=> true
* FHelp.isNotValid(undefined); //=> true
* FHelp.isNotValid(NaN); //=> true
* FHelp.isNotValid([]); //=> true
* FHelp.isNotValid(''); //=> true
* FHelp.isNotValid({}); //=> true
* FHelp.isNotValid(Uint8Array.from('')); //=> true
* FHelp.isNotValid({length: 0}); //=> false
* FHelp.isNotValid([1, 2, 3]); //=> false
* FHelp.isNotValid(0); //=> false
*/
static isNotValid(param) {
return isNil(param) || identical(param, NaN) || isEmpty(param);
}
/**
* 判断给定参数是否有效
* @summary 参数有效
* @param {any} param
* @return {boolean} true: 有效
* @static
* @example
* FHelp.isValid(value) // => !FHelp.isNotValid(value)
*/
static isValid(param) {
return !FHelp.isNotValid(param);
}
/**
* 获取 min 到 max 之间的随机数
* @summary 范围随机值
* @param {number} [min=0] 最小值
* @param {number} [max=1] 最大值
* @param {'floor'|'ceil'|null} [round=null] 取整
* @return {number} 随机值
* @static
* @example
* FHelp.random(0.5, 10, null) // => [0.5, 10) 0.5<=x<10 之间的任意数
* FHelp.random(0.5, 10, 'floor') // => [0, 9] 0-9 之间的整数
* FHelp.random(0.5, 10, 'ceil') // => [1, 10] 1-10 之间的整数
*/
static random(min = 0, max = 1, round = null) {
if (min > max) max = min;
const v = Math.random() * (max - min) + min;
if (round === 'floor') {
return Math.floor(v);
}
else if (round === 'ceil') {
return Math.ceil(v);
}
return v;
}
/**
* 根据对每个元素调用键返回函数的结果,将列表拆分为存储在对象中的子列表,并根据返回的值对结果进行分组
* @summary 分组
* @template T 数组类型
* @template {string | number | Symbol} K key 类型
* @param {FHelp~TParam<T, K>} fn 分组函数
* @param {Array<T>} list 数据集
* @return {Record<K, Array<T>>} 一个对象,输出键值为fn,映射到传递给fn时产生该键的元素数组。
* @static
* @example
* const byGrade = function(student) {
* const score = student.score;
* return score < 65 ? 'F' :
* score < 70 ? 'D' :
* score < 80 ? 'C' :
* score < 90 ? 'B' : 'A';
* };
* const students = [{name: 'Abby', score: 84},
* {name: 'Eddy', score: 58},
* // ...
* {name: 'Jack', score: 69}];
* FHelp.groupBy(byGrade, students);
* // {
* // 'A': [{name: 'Dianne', score: 99}],
* // 'B': [{name: 'Abby', score: 84}]
* // // ...,
* // 'F': [{name: 'Eddy', score: 58}]
* // }
*/
static groupBy(fn, list) {
return groupBy(fn)(list);
}
/**
* 查看一个对象(例如val)是否是所提供构造函数的实例。这个函数将检查继承链(如果有的话)。
*
* 如果val是用Object创建的。create (Object, val) === true
*
* @summary 判断 val 是否是 classes 的子类实例
* @param {Object} classes 类型
* @param {InstanceType<Object>} val 实例
* @return {boolean}
* @static
* @example
* FHelp.is(Object, {}); //=> true
* FHelp.is(Number, 1); //=> true
* FHelp.is(Object, 1); //=> false
* FHelp.is(String, 's'); //=> true
* FHelp.is(String, new String('')); //=> true
* FHelp.is(Object, new String('')); //=> true
* FHelp.is(Object, 's'); //=> false
* FHelp.is(Number, {}); //=> false
*/
static is(classes, val) {
return is(classes, val);
}
/**
* 从对象列表中创建一个具有自己属性的新对象。如果一个键存在于多个对象中,则将使用它存在的最后一个对象中的值。
* @summary 深度合并
* @param {...any} params
* @return {any}
* @static
* @example
* FHelp.mergeAll({foo:1},{bar:2},{baz:3}); //=> {foo:1,bar:2,baz:3}
* FHelp.mergeAll({foo:1},{foo:2},{bar:2}); //=> {foo:2,bar:2}
*/
static mergeAll(...params) {
return mergeAll(params);
}
/**
* 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在:
*
* - 并且两个关联的值都是对象,则继续递归合并这两个值。
* - 否则,使用给定函数对两个值进行处理,并将返回值作为该 key 的新值。
*
* 如果某 key 只存在于一个对象中,该键值对将作为结果对象的键值对。
* @summary 深度合并
* @param {...any} params
* @return {any}
* @static
* @example
* mergeDeepWith({ a: true, c: { values: [10, 20] }},
* { b: true, c: { values: [15, 35] }});
* //=> { a: true, b: true, c: { values: [10, 20, 15, 35] }}
*/
static mergeDeepWith(...params) {
let obj = {};
for (const param of params) {
obj = mergeDeepWith(concatValue, obj, param);
}
return obj;
function concatValue(a, b) {
if (is(Array, a) && is(Array, b)) {
return a.concat(b);
}
return b;
}
}
/**
* 一个总是返回 `true` 的函数。任何传入的参数都将被忽略
* @summary True 回调
* @return {true}
* @static
* @example
* FHelp.F(); //=> true
*/
static T() {
return true;
}
/**
* 一个总是返回 `false` 的函数。任何传入的参数都将被忽略
* @summary False 回调
* @return {false}
* @static
* @example
* FHelp.F(); //=> false
*/
static F() {
return false;
}
/**
* 将 number 限制在某个范围内
*
* 也适用于其他有序类型,如字符串和日期
*
* @summary 限制范围
* @param {number} min clamp 的最小值 (包括 min)
* @param {number} max clamp 的最大值 (包括 max)
* @param {number} value 值
* @return {number} 当 `val < min` 时返回 `min`, 当 `val > max` 时返回 `max`, 否则返回 `value`
* @static
* @example
* FHelp.clamp(1, 10, -5) // => 1
* FHelp.clamp(1, 10, 15) // => 10
* FHelp.clamp(1, 10, 4) // => 4
*/
static clamp(min, max, value) {
if (min > max) max = min;
return clamp(min, max, value);
}
/**
* 如果第二个参数不是 null、undefined 或 NaN,则返回第二个参数,否则返回第一个参数(默认值)
*
* @summary 默认值
* @template T 默认值类型
* @template V 参数类型
* @param {T} def 默认值
* @param {V} val 当前值
* @return {T | V} `val` 无效则是 `def`, 否则是 `val`
* @static
* @example
* FHelp.defaultTo(42, null); //=> 42
* FHelp.defaultTo(42, undefined); //=> 42
* FHelp.defaultTo(42, false); //=> false
* FHelp.defaultTo(42, 'Ramda'); //=> 'Ramda'
* // parseInt('string') results in NaN
* FHelp.defaultTo(42, parseInt('string')); //=> 42
*/
static defaultTo(def, val) {
return defaultTo(def, val);
}
/**
* 在初始化阶段打印项目信息
* @summary 打印信息
* @static
*/
static sayHello() {
const params = [
'\n',
'%c🎉🎉🎉' +
'%c ✨ wl-live2d v1.0.4 - 欢迎你的使用 !! ✨ ' +
'%c🎉🎉🎉',
`%c\n\n`
];
const format = [
`background: linear-gradient(to right, #ff00cc, #ffcc00);padding: 8px 30px;font-size: 1em;`,
`background: linear-gradient(to right, #ff4fa9, #ffa233) text, linear-gradient(to right, #ffcc00, #6fff66, #00ffcc) border-box;color: transparent;padding: 8px 30px;font-size: 1em;`,
`background: linear-gradient(to right, #00ffcc, #ff00cc);padding: 8px 30px;font-size: 1em;`,
``
];
console.log(params.join(''), ...format);
}
}