ItemSelector

An gui for users to select items from given standardized json

当前为 2023-01-14 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/458132/1138309/ItemSelector.js

  1. /* eslint-disable no-multi-spaces */
  2. /* eslint-disable dot-notation */
  3.  
  4. // ==UserScript==
  5. // @name ItemSelector
  6. // @namespace ItemSelector
  7. // @version 0.2
  8. // @description An gui for users to select items from given standardized json
  9. // @author PY-DNG
  10. // @license GPL-v3
  11. // ==/UserScript==
  12.  
  13. /* global structuredClone */
  14. let ItemSelector = (function() {
  15. // function DoLog() {}
  16. // Arguments: level=LogLevel.Info, logContent, trace=false
  17. const [LogLevel, DoLog] = (function() {
  18. const LogLevel = {
  19. None: 0,
  20. Error: 1,
  21. Success: 2,
  22. Warning: 3,
  23. Info: 4,
  24. };
  25.  
  26. return [LogLevel, DoLog];
  27. function DoLog() {
  28. // Get window
  29. const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
  30.  
  31. const LogLevelMap = {};
  32. LogLevelMap[LogLevel.None] = {
  33. prefix: '',
  34. color: 'color:#ffffff'
  35. }
  36. LogLevelMap[LogLevel.Error] = {
  37. prefix: '[Error]',
  38. color: 'color:#ff0000'
  39. }
  40. LogLevelMap[LogLevel.Success] = {
  41. prefix: '[Success]',
  42. color: 'color:#00aa00'
  43. }
  44. LogLevelMap[LogLevel.Warning] = {
  45. prefix: '[Warning]',
  46. color: 'color:#ffa500'
  47. }
  48. LogLevelMap[LogLevel.Info] = {
  49. prefix: '[Info]',
  50. color: 'color:#888888'
  51. }
  52. LogLevelMap[LogLevel.Elements] = {
  53. prefix: '[Elements]',
  54. color: 'color:#000000'
  55. }
  56.  
  57. // Current log level
  58. DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  59.  
  60. // Log counter
  61. DoLog.logCount === undefined && (DoLog.logCount = 0);
  62.  
  63. // Get args
  64. let [level, logContent, trace] = parseArgs([...arguments], [
  65. [2],
  66. [1,2],
  67. [1,2,3]
  68. ], [LogLevel.Info, 'DoLog initialized.', false]);
  69.  
  70. // Log when log level permits
  71. if (level <= DoLog.logLevel) {
  72. let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
  73. let subst = LogLevelMap[level].color;
  74.  
  75. switch (typeof(logContent)) {
  76. case 'string':
  77. msg += '%s';
  78. break;
  79. case 'number':
  80. msg += '%d';
  81. break;
  82. default:
  83. msg += '%o';
  84. break;
  85. }
  86.  
  87. if (++DoLog.logCount > 512) {
  88. console.clear();
  89. DoLog.logCount = 0;
  90. }
  91. console[trace ? 'trace' : 'log'](msg, subst, logContent);
  92. }
  93. }
  94. }) ();
  95.  
  96. return ItemSelector;
  97.  
  98. function ItemSelector(useWrapper=true) {
  99. const IS = this;
  100. const DATA = {
  101. showing: false, json: null, data: null, options: null
  102. };
  103. const elements = IS.elements = {};
  104. defineGetter(IS, 'showing', () => DATA.showing);
  105. defineGetter(IS, 'json', () => MakeReadonlyObj(DATA.json));
  106. defineGetter(IS, 'data', () => MakeReadonlyObj(DATA.data));
  107. defineGetter(IS, 'options', () => MakeReadonlyObj(DATA.options));
  108. IS.show = show;
  109. IS.close = close;
  110. IS.getSelectedItems = getSelectedItems;
  111. init();
  112.  
  113. function init() {
  114. const wrapper = useWrapper ? (function() {
  115. const wrapper = elements.wrapper = $CrE(randstr(4, false, false) + '-' + randstr(4, false, false));
  116. const shadow = wrapper.attachShadow({mode: 'closed'});
  117. document.body.appendChild(wrapper);
  118. return shadow;
  119. }) () : document.body;
  120. const container = elements.container = $CrE('div');
  121. const header = elements.header = $CrE('div');
  122. const body = elements.body = $CrE('div');
  123. const footer = elements.footer = $CrE('div');
  124. container.classList.add('itemselector-container');
  125. header.classList.add('itemselector-header');
  126. body.classList.add('itemselector-body');
  127. footer.classList.add('itemselector-footer');
  128. container.appendChild(header);
  129. container.appendChild(body);
  130. container.appendChild(footer);
  131. wrapper.appendChild(container);
  132.  
  133. const title = elements.title = $CrE('span');
  134. title.classList.add('itemselector-title');
  135. header.appendChild(title);
  136.  
  137. const bglist = elements.bglist = $CrE('div');
  138. bglist.classList.add('itemselector-bglist');
  139. body.appendChild(bglist);
  140.  
  141. const list = elements.list = $CrE('pre');
  142. list.classList.add('itemselector-list');
  143. body.appendChild(list);
  144.  
  145. const btnOK = $CrE('button');
  146. const btnCancel = $CrE('button');
  147. const btnClose = $CrE('button');
  148. btnOK.innerText = 'OK';
  149. btnCancel.innerText = 'Cancel';
  150. btnClose.innerText = 'x';
  151. btnOK.className = 'itemselector-button itemselector-button-ok';
  152. btnCancel.className = 'itemselector-button itemselector-button-cancel';
  153. btnClose.className = 'itemselector-button itemselector-button-close';
  154. $AEL(btnOK, 'click', ok_onClick);
  155. $AEL(btnCancel, 'click', cancel_onClick);
  156. $AEL(btnClose, 'click', close_onClick);
  157. header.appendChild(btnClose);
  158. footer.appendChild(btnCancel);
  159. footer.appendChild(btnOK);
  160. elements.button = {btnOK, btnCancel, btnClose};
  161.  
  162. const cssParent = useWrapper ? wrapper : document.head;
  163. const css = '.itemselector-container {display: none;position: fixed;position: fixed;width: 60vw;height: 60vh;left: 20vw;top: 20vh;border-radius: 1em;padding: 2em;user-select: none;font-family: -apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol!important;}.itemselector-container.itemselector-show {display: block;}.itemselector-header {position: absolute;width: calc(100% - 4em);padding-bottom: 0.3em;}.itemselector-title {position: relative;font-size: 1.3em;}.itemselector-body {position: absolute;top: calc(2em + 20px * 1.3 + 20px * 0.3 + 1px + 0.3em);bottom: calc(2em + 20px + 20px + calc(60vw - 4em) * 2 / 100 + 0.3em);overflow: auto;width: calc(100% - 4em);z-index: -2;}.itemselector-bglist {position: absolute;left: 0;width: 100%;z-index: -1;}.itemselector-footer {position: absolute;bottom: 2em;width: calc(100% - 4em);}.itemselector-button {font-size: 20px;width: 48%;margin: 1%;border: none;border-radius: 3px;padding: 0.5em;font-weight: 500;}.itemselector-button.itemselector-button-close {position: relative;float: right;margin: 0;padding: 0;width: 1.3em;height: 1.3em;text-align: center;font-size: 20px;}.itemselector-list {margin: 0;pointer-events: none;}.itemselector-item {margin: 0;margin-left: 1em;}.itemselector-item-root {margin-left: 0;}.itemselector-item-background {width: 100%;height: 49px;}.itemselector-item-background:first-child {border-top: none;}.itemselector-item-self {font-size: 14px;line-height: 34px;padding: 8px;background-color: rgba(0,0,0,0);pointer-events: auto;}.itemselector-toggle {position: relative;visibility: hidden;}.itemselector-toggle.itemselector-show {visibility: visible;}.itemselector-toggle:before {content: "\\25BC";width: 1em;display: inline-block;position: relative;}.itemselector-item-collapsed>.itemselector-item-self>.itemselector-toggle:before {content: "\\25B6";}.itemselector-item-collapsed>.itemselector-item-child>.itemselector-item {display: none;}.itemselector-text {pointer-events: none;}:root.light {--itemselector-color: #000;--itemselector-bgcolor-1: #dddddd;--itemselector-bgcolor-0: #e2e2e2;--itemselector-bgcolor-2: #cdcdcd;--itemselector-bgcolor-3: #bdbdbd;--itemselector-btnclose-bgcolor: #00bcd4;--itemselector-spliter-color: rgba(0,0,0,0.28);}:root.dark {--itemselector-color: #fff;--itemselector-bgcolor-0: #1d1d1d;--itemselector-bgcolor-1: #222222;--itemselector-bgcolor-2: #323232;--itemselector-bgcolor-3: #424242;--itemselector-btnclose-bgcolor: #00bcd4;--itemselector-spliter-color: rgba(255,255,255,0.28);}.itemselector-container {box-shadow: 0 3px 15px rgb(0 0 0 / 20%), 0 6px 6px rgb(0 0 0 / 14%), 0 9px 3px -6px rgb(0 0 0 / 12%);color: var(--itemselector-color);background-color: var(--itemselector-bgcolor-0);}.itemselector-header {border-bottom: 1px solid var(--itemselector-spliter-color);}.itemselector-body {scrollbar-color: var(--itemselector-bgcolor-2) var(--itemselector-bgcolor-1);}.itemselector-body:hover {scrollbar-color: var(--itemselector-bgcolor-3) var(--itemselector-bgcolor-1);}.itemselector-body::-webkit-scrollbar {background-color: var(--itemselector-bgcolor-1);}.itemselector-body::-webkit-scrollbar-thumb, .itemselector-body::-webkit-scrollbar-button {background-color: var(--itemselector-bgcolor-2);}.itemselector-body::-webkit-scrollbar-thumb:hover, .itemselector-body::-webkit-scrollbar-button:hover {background-color: var(--itemselector-bgcolor-3);}.itemselector-item-background {transition-duration: 0.3s;border-top: 1px solid var(--itemselector-spliter-color);}.itemselector-item-background.itemselector-item-hover {background-color: var(--itemselector-bgcolor-2);}.itemselector-button {background-color: var(--itemselector-btnclose-bgcolor);color: var(--itemselector-color);}.itemselector-button.itemselector-button-close {background-color: var(--itemselector-bgcolor-2);}.itemselector-button.itemselector-button-close:hover {background-color: var(--itemselector-bgcolor-3);}';
  164. const style = $CrE('style');
  165. style.innerHTML = css;
  166. cssParent.appendChild(style);
  167.  
  168. function ok_onClick(e) {
  169. if (!DATA.showing) {
  170. DoLog(LogLevel.Warning, 'ok_onClick invoked when dialog is not showing');
  171. return false;
  172. }
  173. if (!DATA.options) {
  174. DoLog(LogLevel.Warning, 'DATA.options missing while ok_onClick invoked');
  175. return false;
  176. }
  177. typeof DATA.options.onok === 'function' && DATA.options.onok.call(this, e, getSelectedItems());
  178. close();
  179. }
  180.  
  181. function cancel_onClick(e) {
  182. if (!DATA.showing) {
  183. DoLog(LogLevel.Warning, 'cancel_onClick invoked when dialog is not showing');
  184. return false;
  185. }
  186. if (!DATA.options) {
  187. DoLog(LogLevel.Warning, 'DATA.options missing while cancel_onClick invoked');
  188. return false;
  189. }
  190. typeof DATA.options.oncancel === 'function' && DATA.options.oncancel.call(this, e, getSelectedItems());
  191. close();
  192. }
  193.  
  194. function close_onClick(e) {
  195. if (!DATA.showing) {
  196. DoLog(LogLevel.Warning, 'close_onClick invoked when dialog is not showing');
  197. return false;
  198. }
  199. if (!DATA.options) {
  200. DoLog(LogLevel.Warning, 'DATA.options missing while close_onClick invoked');
  201. return false;
  202. }
  203. typeof DATA.options.onclose === 'function' && DATA.options.onclose.call(this, e, getSelectedItems());
  204. close();
  205. }
  206. }
  207.  
  208. function show(json, options={title: ''}) {
  209. // Status check & update
  210. if (!json) {
  211. DoLog(LogLevel.Error, 'json missing');
  212. return false;
  213. }
  214. if (DATA.showing) {
  215. DoLog(LogLevel.Error, 'show invoked while DATA.showing === true');
  216. return false;
  217. }
  218. DATA.showing = true;
  219. DATA.options = options;
  220. DATA.json = structuredClone(json);
  221. DATA.data = makeData(json);
  222.  
  223. // elements
  224. const {container, header, title, body, footer, bglist, list} = elements;
  225.  
  226. // make new <ul>
  227. const ul = makeListItem(json);
  228. ul.classList.add('itemselector-item-root');
  229. [...list.children].forEach(c => c.remove());
  230. list.appendChild(ul);
  231.  
  232. // configure with options
  233. options.hasOwnProperty('title') && (title.innerText = options.title);
  234.  
  235. // display container
  236. updateElementSelect();
  237. container.classList.add('itemselector-show');
  238.  
  239. return IS;
  240.  
  241. function makeListItem(json_item, path=[]) {
  242. const item = pathItem(path);
  243. const hasChild = Array.isArray(item.children);
  244.  
  245. // create new div
  246. const div = item.elements.div = $CrE('div');
  247. const self_container = item.elements.self_container = $CrE('div');
  248. const child_container = item.elements.child_container = $CrE('div');
  249. const background = item.elements.background = $CrE('div');
  250. div.classList.add('itemselector-item');
  251. self_container.classList.add('itemselector-item-self');
  252. child_container.classList.add('itemselector-item-child');
  253. background.classList.add('itemselector-item-background');
  254. hasChild && div.classList.add('itemselector-item-parent');
  255. $AEL(background, 'mouseenter', e => background.classList.add('itemselector-item-hover'));
  256. $AEL(background, 'mouseleave', e => background.classList.remove('itemselector-item-hover'));
  257. $AEL(self_container, 'mouseenter', e => background.classList.add('itemselector-item-hover'));
  258. $AEL(self_container, 'mouseleave', e => background.classList.remove('itemselector-item-hover'));
  259. bglist.appendChild(background);
  260. div.appendChild(self_container);
  261. div.appendChild(child_container);
  262.  
  263. // triangle toggle for folder items
  264. const toggle = item.elements.toggle = $CrE('a');
  265. toggle.classList.add('itemselector-toggle');
  266. hasChild && toggle.classList.add('itemselector-show');
  267. $AEL(toggle, 'click', e => {
  268. destroyEvent(e);
  269. div.classList[[...div.classList].includes('itemselector-item-collapsed') ? 'remove' : 'add']('itemselector-item-collapsed');
  270. });
  271. self_container.appendChild(toggle);
  272.  
  273. // checkbox for selecting
  274. const checkbox = item.elements.checkbox = $CrE('input');
  275. checkbox.type = 'checkbox';
  276. checkbox.classList.add('itemselector-checker');
  277. $AEL(checkbox, 'change', checkbox_onChange);
  278. self_container.appendChild(checkbox);
  279.  
  280. // check checkbox when self_container or background block onclick
  281. const clickTargets = [self_container, background]
  282. clickTargets.forEach(elm => $AEL(elm, 'click', function(e) {
  283. if (clickTargets.includes(e.target)) {
  284. checkbox.checked = !checkbox.checked;
  285. checkbox_onChange();
  286. }
  287. }));
  288.  
  289. // item text
  290. const text = item.elements.text = $CrE('span');
  291. text.classList.add('itemselector-text');
  292. text.innerText = json_item.text;
  293. self_container.appendChild(text);
  294.  
  295. // make child items
  296. if (hasChild) {
  297. item.elements.children = [];
  298. for (let i = 0; i < json_item.children.length; i++) {
  299. const childItem = makeListItem(json_item.children[i], [...path, i]);
  300. item.elements.children.push(childItem);
  301. child_container.appendChild(childItem);
  302. }
  303. }
  304.  
  305. return div;
  306.  
  307. function checkbox_onChange(e) {
  308. // set select status
  309. item.selected = checkbox.checked;
  310.  
  311. // update element
  312. updateElementSelect();
  313. }
  314. }
  315. }
  316.  
  317. function close() {
  318. if (!DATA.showing) {
  319. DoLog(LogLevel.Error, 'show invoked while DATA.showing === false');
  320. return false;
  321. }
  322. DATA.showing = false;
  323. DATA.options = null;
  324.  
  325. elements.container.classList.remove('itemselector-show');
  326. }
  327.  
  328. function updateElementSelect() {
  329. //const data = DATA.data;
  330. update(DATA.data);
  331.  
  332. function update(item) {
  333. // item elements
  334. const elements = item.elements;
  335. const checkbox = elements.checkbox;
  336.  
  337. // props
  338. checkbox.checked = item.selected;
  339. checkbox.indeterminate = item.childSelected && !item.selected;
  340.  
  341. // update children
  342. if (Array.isArray(item.children)) {
  343. for (const child of item.children) {
  344. update(child);
  345. }
  346. }
  347. }
  348. }
  349.  
  350. function getSelectedItems() {
  351. const json = structuredClone(DATA.json);
  352. const data = DATA.data;
  353. const MARK = Symbol('cut-mark');
  354.  
  355. mark(json, data);
  356. return cut(json);
  357.  
  358. function mark(json_item, data_item) {
  359. if (!data_item.selected && !data_item.childSelected) {
  360. json_item[MARK] = true;
  361. } else if (Array.isArray(data_item.children)) {
  362. for (let i = 0; i < data_item.children.length; i++) {
  363. mark(json_item.children[i], data_item.children[i]);
  364. }
  365. }
  366. }
  367.  
  368. function cut(json_item) {
  369. if (json_item[MARK]) {
  370. return null;
  371. } else {
  372. const children = json_item.children;
  373. if (Array.isArray(children)) {
  374. for (const cutchild of children.filter(child => child[MARK])) {
  375. children.splice(children.indexOf(cutchild), 1);
  376. }
  377. children.forEach((child, i) => {
  378. children[i] = cut(child);
  379. });
  380. }
  381. return json_item;
  382. }
  383. }
  384. }
  385.  
  386. function pathItem(path) {
  387. return pathObj(DATA.data, path);
  388. }
  389.  
  390. function pathObj(obj, path) {
  391. let target = obj;
  392. const _path = [...path];
  393. while (_path.length) {
  394. target = target.children[_path.shift()];
  395. }
  396. return target;
  397. }
  398.  
  399. function makeData(json) {
  400. return proxyItemData(makeItemData(json));
  401.  
  402. function proxyItemData(data) {
  403. return typeof data === 'object' && data !== null ? new Proxy(data, {
  404. get: function(target, property, receiver) {
  405. const value = target[property];
  406. const noproxy = typeof value === 'object' && value !== null && value['__NOPROXY__'] === true;
  407. return noproxy ? value : proxyItemData(value);
  408. },
  409. set: function(target, property, value, receiver) {
  410. switch (property) {
  411. case 'selected':
  412. // set item and its children's selected status by rule
  413. select(target, value, !value);
  414. break;
  415. default:
  416. // setting other props are not allowed
  417. break;
  418. }
  419. return true;
  420.  
  421. function select(item, selected) {
  422. // write item
  423. item.selected = selected;
  424.  
  425. // write children selected
  426. select_children(item)
  427.  
  428. // write parent selected
  429. select_parent(item);
  430.  
  431. // calculate children childSelected
  432. childSelected_children(item);
  433.  
  434. // calculate parent childSelected
  435. childSelected_parent(item);
  436.  
  437. function select_children(item) {
  438. if (Array.isArray(item.children)) {
  439. for (const child of item.children) {
  440. if (child.selected !== selected) {
  441. child.selected = selected;
  442. select_children(child, selected);
  443. }
  444. }
  445. }
  446. }
  447.  
  448. function select_parent(item) {
  449. if (item.parent) {
  450. const parent = item.parent;
  451. const selected = parent.children.every(child => child.selected);
  452. if (parent.selected !== selected) {
  453. parent.selected = selected;
  454. select_parent(parent);
  455. }
  456. }
  457. }
  458.  
  459. function childSelected_children(item) {
  460. if (Array.isArray(item.children)) {
  461. for (const child of item.children) {
  462. childSelected_children(child);
  463. }
  464. item.childSelected = item.children.some(child => child.selected || child.childSelected);
  465. } else {
  466. item.childSelected = false;
  467. }
  468. }
  469.  
  470. function childSelected_parent(item) {
  471. if (item.parent) {
  472. const parent = item.parent;
  473. const childSelected = parent.children.some(child => child.selected || child.childSelected);
  474. if (parent.childSelected !== childSelected) {
  475. parent.childSelected = childSelected;
  476. childSelected_parent(parent);
  477. }
  478. }
  479. }
  480. }
  481. }
  482. }) : data;
  483. }
  484.  
  485. function makeItemData(json, parent=null) {
  486. const hasChild = Array.isArray(json.children);
  487. const item = {};
  488. item.elements = {__NOPROXY__:true};
  489. item.selected = true;
  490. item.childSelected = hasChild && json.children.length > 0;
  491. item.parent = parent !== null && typeof parent === 'object' ? parent : null;
  492. if (hasChild) {
  493. item.children = json.children.map(child => makeItemData(child, item));
  494. }
  495. return item;
  496. }
  497. }
  498.  
  499. function defineGetter(obj, prop, getter) {
  500. Object.defineProperty(obj, prop, {
  501. get: getter,
  502. set: v => true,
  503. configurable: false,
  504. enumerable: true,
  505. });
  506. }
  507. }
  508.  
  509. // Basic functions
  510. // querySelector
  511. function $() {
  512. switch(arguments.length) {
  513. case 2:
  514. return arguments[0].querySelector(arguments[1]);
  515. break;
  516. default:
  517. return document.querySelector(arguments[0]);
  518. }
  519. }
  520. // querySelectorAll
  521. function $All() {
  522. switch(arguments.length) {
  523. case 2:
  524. return arguments[0].querySelectorAll(arguments[1]);
  525. break;
  526. default:
  527. return document.querySelectorAll(arguments[0]);
  528. }
  529. }
  530. // createElement
  531. function $CrE() {
  532. switch(arguments.length) {
  533. case 2:
  534. return arguments[0].createElement(arguments[1]);
  535. break;
  536. default:
  537. return document.createElement(arguments[0]);
  538. }
  539. }
  540. // addEventListener
  541. function $AEL(...args) {
  542. const target = args.shift();
  543. return target.addEventListener.apply(target, args);
  544. }
  545.  
  546. // Just stopPropagation and preventDefault
  547. function destroyEvent(e) {
  548. if (!e) {return false;};
  549. if (!e instanceof Event) {return false;};
  550. e.stopPropagation();
  551. e.preventDefault();
  552. }
  553.  
  554. function parseArgs(args, rules, defaultValues=[]) {
  555. // args and rules should be array, but not just iterable (string is also iterable)
  556. if (!Array.isArray(args) || !Array.isArray(rules)) {
  557. throw new TypeError('parseArgs: args and rules should be array')
  558. }
  559.  
  560. // fill rules[0]
  561. (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
  562.  
  563. // max arguments length
  564. const count = rules.length - 1;
  565.  
  566. // args.length must <= count
  567. if (args.length > count) {
  568. throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
  569. }
  570.  
  571. // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
  572. for (let i = 1; i <= count; i++) {
  573. const rule = rules[i];
  574. if (Array.isArray(rule)) {
  575. if (rule.length !== i) {
  576. throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
  577. }
  578. if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
  579. throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
  580. }
  581. } else if (typeof rule !== 'function') {
  582. throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
  583. }
  584. }
  585.  
  586. // Parse
  587. const rule = rules[args.length];
  588. let parsed;
  589. if (Array.isArray(rule)) {
  590. parsed = [...defaultValues];
  591. for (let i = 0; i < rule.length; i++) {
  592. parsed[rule[i]-1] = args[i];
  593. }
  594. } else {
  595. parsed = rule(args, defaultValues);
  596. }
  597. return parsed;
  598. }
  599.  
  600. function MakeReadonlyObj(val) {
  601. return isObject(val) ? new Proxy(val, {
  602. get: function(target, property, receiver) {
  603. return MakeReadonlyObj(target[property]);
  604. },
  605. set: function(target, property, value, receiver) {
  606. return true;
  607. }
  608. }) : val;
  609.  
  610. function isObject(value) {
  611. return ['object', 'function'].includes(typeof value) && value !== null;
  612. }
  613. }
  614.  
  615. // Returns a random string
  616. function randstr(length=16, nums=true, cases=true) {
  617. const all = 'abcdefghijklmnopqrstuvwxyz' + (nums ? '0123456789' : '') + (cases ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' : '');
  618. return Array(length).fill(0).reduce(pre => (pre += all.charAt(randint(0, all.length-1))), '');
  619. }
  620.  
  621. function randint(min, max) {
  622. return Math.floor(Math.random() * (max - min + 1)) + min;
  623. }
  624. }) ();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址