1/**
2 * Created by Liu.Jun on 2020/4/17 17:05.
3 */
4
5// is object
6export function isObject(object) {
7    return Object.prototype.toString.call(object) === '[object Object]';
8}
9
10// is arguments
11function isArguments(object) {
12    return Object.prototype.toString.call(object) === '[object Arguments]';
13}
14
15// 定义的数据推导出schema 类型
16export const guessType = function guessType(value) {
17    if (Array.isArray(value)) {
18        return 'array';
19    } if (typeof value === 'string') {
20        return 'string';
21    } if (value == null) {
22        return 'null';
23    } if (typeof value === 'boolean') {
24        return 'boolean';
25        // eslint-disable-next-line no-restricted-globals
26    } if (!isNaN(value)) {
27        return 'number';
28    } if (typeof value === 'object') {
29        return 'object';
30    }
31    // Default to string if we can't figure it out
32    return 'string';
33};
34
35export function union(arr1, arr2) {
36    return [...new Set([...arr1, ...arr2])];
37}
38
39// Recursively merge deeply nested schemas.
40// The difference between mergeSchemas and mergeObjects
41// is that mergeSchemas only concats arrays for
42// values under the "required" keyword, and when it does,
43// it doesn't include duplicate values.
44export function mergeSchemas(obj1, obj2) {
45    const acc = Object.assign({}, obj1); // Prevent mutation of source object.
46    // eslint-disable-next-line no-shadow
47    return Object.keys(obj2).reduce((acc, key) => {
48        const left = obj1 ? obj1[key] : {};
49        const right = obj2[key];
50        if (obj1 && obj1.hasOwnProperty(key) && isObject(right)) {
51            acc[key] = mergeSchemas(left, right);
52        } else if (
53            obj1
54            && obj2
55            && (getSchemaType(obj1) === 'object' || getSchemaType(obj2) === 'object')
56            && key === 'required'
57            && Array.isArray(left)
58            && Array.isArray(right)
59        ) {
60            // Don't include duplicate values when merging
61            // "required" fields.
62            acc[key] = union(left, right);
63        } else {
64            acc[key] = right;
65        }
66        return acc;
67    }, acc);
68}
69
70// 合并对象数据
71export function mergeObjects(obj1, obj2, concatArrays = false) {
72    // Recursively merge deeply nested objects.
73    const preAcc = Object.assign({}, obj1); // Prevent mutation of source object.
74    if (!isObject(obj2)) return preAcc;
75
76    return Object.keys(obj2).reduce((acc, key) => {
77        const left = obj1 ? obj1[key] : {};
78        const right = obj2[key];
79        if (obj1 && obj1.hasOwnProperty(key) && isObject(right)) {
80            acc[key] = mergeObjects(left, right, concatArrays);
81        } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
82            acc[key] = left.concat(right);
83        } else {
84            acc[key] = right;
85        }
86        return acc;
87    }, preAcc);
88}
89
90// 获取给定 schema 类型。
91export function getSchemaType(schema) {
92    const { type } = schema;
93
94    // 通过const 申明的常量 做类型推断
95    if (!type && schema.const) {
96        return guessType(schema.const);
97    }
98
99    // 枚举默认字符串
100    if (!type && schema.enum) {
101        return 'string';
102    }
103
104    // items 推断为 array 类型
105    if (!type && (schema.items)) {
106        return 'array';
107    }
108
109    // anyOf oneOf 不申明 type 字段
110    if (!type && (schema.properties || schema.additionalProperties)) {
111        return 'object';
112    }
113
114    if (type instanceof Array && type.length === 2 && type.includes('null')) {
115        return type.find(curType => curType !== 'null');
116    }
117
118    return type;
119}
120
121// 深度相等对比
122export function deepEquals(a, b, ca = [], cb = []) {
123    // Partially extracted from node-deeper and adapted to exclude comparison
124    // checks for functions.
125    // https://github.com/othiym23/node-deeper
126    if (a === b) {
127        return true;
128    } if (typeof a === 'function' || typeof b === 'function') {
129        // Assume all functions are equivalent
130        // see https://github.com/mozilla-services/react-jsonschema-form/issues/255
131        return true;
132    } if (typeof a !== 'object' || typeof b !== 'object') {
133        return false;
134    } if (a === null || b === null) {
135        return false;
136    } if (a instanceof Date && b instanceof Date) {
137        return a.getTime() === b.getTime();
138    } if (a instanceof RegExp && b instanceof RegExp) {
139        return (
140            a.source === b.source
141            && a.global === b.global
142            && a.multiline === b.multiline
143            && a.lastIndex === b.lastIndex
144            && a.ignoreCase === b.ignoreCase
145        );
146    } if (isArguments(a) || isArguments(b)) {
147        if (!(isArguments(a) && isArguments(b))) {
148            return false;
149        }
150        const slice = Array.prototype.slice;
151        return deepEquals(slice.call(a), slice.call(b), ca, cb);
152    }
153    if (a.constructor !== b.constructor) {
154        return false;
155    }
156
157    const ka = Object.keys(a);
158    const kb = Object.keys(b);
159    // don't bother with stack acrobatics if there's nothing there
160    if (ka.length === 0 && kb.length === 0) {
161        return true;
162    }
163    if (ka.length !== kb.length) {
164        return false;
165    }
166
167    let cal = ca.length;
168    // eslint-disable-next-line no-plusplus
169    while (cal--) {
170        if (ca[cal] === a) {
171            return cb[cal] === b;
172        }
173    }
174    ca.push(a);
175    cb.push(b);
176
177    ka.sort();
178    kb.sort();
179    // eslint-disable-next-line no-plusplus
180    for (let j = ka.length - 1; j >= 0; j--) {
181        if (ka[j] !== kb[j]) {
182            return false;
183        }
184    }
185
186    let key;
187    // eslint-disable-next-line no-plusplus
188    for (let k = ka.length - 1; k >= 0; k--) {
189        key = ka[k];
190        if (!deepEquals(a[key], b[key], ca, cb)) {
191            return false;
192        }
193    }
194
195    ca.pop();
196    cb.pop();
197
198    return true;
199}
200
201// 只保证同时生成不重复
202export const genId = (function genIdFn() {
203    let preKey = `${+new Date()}`;
204    let key = 0;
205    return () => {
206        const curTimestamp = `${+new Date()}`;
207        if (curTimestamp === preKey) {
208            key += 1;
209        } else {
210            // 重置 key
211            key = 0;
212        }
213
214        preKey = curTimestamp;
215        return `${preKey}x${key}`;
216    };
217}());
218
219// 空对象
220export function isEmptyObject(obj) {
221    if (!obj) return true;
222
223    for (const key in obj) {
224        if (Object.prototype.hasOwnProperty.call(obj, key)) {
225            return false;
226        }
227    }
228    return true;
229}
230
231// 过滤和转换对象的key
232export function filterObject(obj, filterFn) {
233    return Object.entries(obj).reduce((preVal, [key, value]) => {
234        const newKey = filterFn(key, value);
235        if (undefined !== newKey) {
236            preVal[newKey] = value;
237        }
238        return preVal;
239    }, {});
240}
241
242const f = s => `0${s}`.substr(-2);
243export function parseDateString(dateString, includeTime = true) {
244    if (!dateString) {
245        return {
246            year: -1,
247            month: -1,
248            day: -1,
249            hour: includeTime ? -1 : 0,
250            minute: includeTime ? -1 : 0,
251            second: includeTime ? -1 : 0,
252        };
253    }
254    const date = new Date(dateString);
255    if (Number.isNaN(date.getTime())) {
256        throw new Error(`Unable to parse date ${dateString}`);
257    }
258    return {
259        year: date.getFullYear(),
260        month: f(date.getMonth() + 1), // oh you, javascript.
261        day: f(date.getDate()),
262        hour: f(includeTime ? date.getHours() : 0),
263        minute: f(includeTime ? date.getMinutes() : 0),
264        second: f(includeTime ? date.getSeconds() : 0),
265    };
266}
267
268export function toDateString(
269    {
270        year, month, day, hour = 0, minute = 0, second = 0
271    },
272    time = true
273) {
274    const utcTime = Date.UTC(year, month - 1, day, hour, minute, second);
275    const datetime = new Date(utcTime).toJSON();
276    return time ? datetime : datetime.slice(0, 10);
277}
278
279export function pad(num, size) {
280    let s = String(num);
281    while (s.length < size) {
282        s = `0${s}`;
283    }
284    return s;
285}
286
287// dataUrl 转 Blob文件对象
288export function dataURItoBlob(dataURI) {
289    // Split metadata from data
290    const splitted = dataURI.split(',');
291    // Split params
292    const params = splitted[0].split(';');
293    // Get mime-type from params
294    const type = params[0].replace('data:', '');
295    // Filter the name property from params
296    const properties = params.filter(param => param.split('=')[0] === 'name');
297    // Look for the name and use unknown if no name property.
298    let name;
299    if (properties.length !== 1) {
300        name = 'unknown';
301    } else {
302        // Because we filtered out the other property,
303        // we only have the name case here.
304        name = properties[0].split('=')[1];
305    }
306
307    // Built the Uint8Array Blob parameter from the base64 string.
308    const binary = atob(splitted[1]);
309    const array = [];
310    // eslint-disable-next-line no-plusplus
311    for (let i = 0; i < binary.length; i++) {
312        array.push(binary.charCodeAt(i));
313    }
314    // Create the blob object
315    const blob = new window.Blob([new Uint8Array(array)], { type });
316
317    return { blob, name };
318}
319
320// 字符串首字母小写
321export function lowerCase(str) {
322    if (undefined === str) return str;
323    return String(str).replace(/^./, s => s.toLocaleLowerCase());
324}
325
326// 最大公约数
327export function gcd(a, b) {
328    if (b === 0) return a;
329    return gcd(b, a % b);
330}
331
332// 最小公倍数
333export function scm(a, b) {
334    return (a * b) / gcd(a, b);
335}
336
337// 打开新页面
338export function openNewPage(url, target = '_blank') {
339    const a = document.createElement('a');
340    a.style.display = 'none';
341    a.target = target;
342    a.href = url;
343    document.body.appendChild(a);
344    a.click();
345    document.body.removeChild(a);
346}
347