SettingPanel

SettingPanel for wenku8++

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/450209/1091923/SettingPanel.js

  1. /* eslint-disable no-multi-spaces */
  2. /* eslint-disable no-implicit-globals */
  3. /* eslint-disable userscripts/no-invalid-headers */
  4. /* eslint-disable userscripts/no-invalid-grant */
  5.  
  6. // ==UserScript==
  7. // @name SettingPanel
  8. // @displayname SettingPanel
  9. // @namespace Wenku8++
  10. // @version 0.3.9
  11. // @description SettingPanel for wenku8++
  12. // @author PY-DNG
  13. // @license GPL-v3
  14. // @regurl NONE
  15. // @require https://gf.qytechs.cn/scripts/449412-basic-functions/code/Basic%20Functions.js?version=1085783
  16. // @require https://gf.qytechs.cn/scripts/449583-configmanager/code/ConfigManager.js?version=1085836
  17. // @grant none
  18. // ==/UserScript==
  19.  
  20. /*
  21. 计划任务:
  22. [x] 表格线换成蓝色的
  23. [x] 允许使用不同的alertify对话框
  24. [x] 点击按钮的时候要有GUI反馈
  25. [ ] 未保存内容,关闭窗口前要有提示
  26. [ ] 提供注册(不可用)SettingOptions组件的接口
  27. */
  28.  
  29. (function __MAIN__() {
  30. 'use strict';
  31.  
  32. const ASSETS = require('assets');
  33. const alertify = require('alertify');
  34. const CONST = {
  35. Text: {
  36. Saved: '已保存',
  37. Reset: '已恢复到修改前'
  38. },
  39. Manager_Config_Ruleset: {
  40. 'version-key': 'config-version',
  41. 'ignores': ["LOCAL-CDN"],
  42. 'defaultValues': {
  43. //'config-key': {},
  44. }
  45. }
  46. };
  47. const SettingOptionElements = {
  48. 'string': {
  49. createElement: function() {const e = $CrE('input'); e.style.width = '90%'; return e;},
  50. setValue: function(val) {this.element.value = val;},
  51. getValue: function() {return this.element.value;},
  52. },
  53. 'number': {
  54. createElement: function() {const e = $CrE('input'); e.type = 'number'; e.style.width = '90%'; return e;},
  55. setValue: function (val) {this.element.value = val;},
  56. getValue: function() {return this.element.value;},
  57. },
  58. 'boolean': {
  59. createElement: function() {const e = $CrE('input'); e.type = 'checkbox'; return e;},
  60. setValue: function(val) {this.element.checked = val;},
  61. getValue: function(data) {return this.element.checked ? (this.hasOwnProperty('data') ? this.data.checked : true) : (data ? this.data.unchecked : false);},
  62. },
  63. 'select': {
  64. createElement: (() => {const e = $CrE('select'); this.hasOwnProperty('data') && this.data.forEach((d) => {const o = $CrE('option'); o.innerText = d; e.appendChild(o)}); return e;}),
  65. setValue: (val) => (Array.from(this.element.children).find((opt) => (opt.value === val)).selected = true),
  66. getValue: function() {return this.element.value;},
  67. }
  68. }
  69.  
  70. // initialize
  71. alertify.dialog('setpanel', function factory(){
  72. return {
  73. // The dialog startup function
  74. // This will be called each time the dialog is invoked
  75. // For example: alertify.myDialog( data );
  76. main:function(){
  77. // Split arguments
  78. let content, header, buttons, onsave, onreset, onclose;
  79. switch (arguments.length) {
  80. case 1:
  81. switch (typeof arguments[0]) {
  82. case 'string':
  83. content = arguments[0];
  84. break;
  85. case 'object':
  86. arguments[0].hasOwnProperty('content') && (content = arguments[0].content);
  87. arguments[0].hasOwnProperty('header') && (header = arguments[0].header);
  88. arguments[0].hasOwnProperty('buttons') && (buttons = arguments[0].buttons);
  89. arguments[0].hasOwnProperty('onsave') && (onsave = arguments[0].onsave);
  90. arguments[0].hasOwnProperty('onreset') && (onreset = arguments[0].onreset);
  91. arguments[0].hasOwnProperty('onclose') && (buttons = arguments[0].onclose);
  92. break;
  93. default:
  94. Err('Arguments invalid', 1);
  95. }
  96. break;
  97. case 2:
  98. content = arguments[0];
  99. header = arguments[1];
  100. break;
  101. case 3:
  102. content = arguments[0];
  103. header = arguments[1];
  104. buttons = buttons[2];
  105. break;
  106. }
  107.  
  108. // Prepare dialog
  109. this.resizeTo('80%', '80%');
  110. content && this.setContent(content);
  111. header && this.setHeader(header);
  112. onsave && this.set('onsave', onsave);
  113. onreset && this.set('onreset', onreset);
  114. onclose && this.set('onclose', onclose);
  115.  
  116. // Choose & show selected button groups
  117. const btnGroups = {
  118. // Close button only
  119. basic: [[1, 0]],
  120.  
  121. // Save & reset button
  122. saver: [[0, 0], [1, 1]]
  123. };
  124. const group = btnGroups[buttons || 'basic'];
  125. const divs = ['auxiliary', 'primary'];
  126. divs.forEach((div) => {
  127. Array.from(this.elements.buttons[div].children).forEach((btn) => {
  128. btn.style.display = 'none';
  129. });
  130. });
  131. group.forEach((button) => {
  132. this.elements.buttons[divs[button[0]]].children[button[1]].style.display = '';
  133. });
  134.  
  135. return this;
  136. },
  137. // The dialog setup function
  138. // This should return the dialog setup object ( buttons, focus and options overrides ).
  139. setup:function(){
  140. return {
  141. /* buttons collection */
  142. buttons:[{
  143. /* button label */
  144. text: '恢复到修改前',
  145.  
  146. /*bind a keyboard key to the button */
  147. key: undefined,
  148.  
  149. /* indicate if closing the dialog should trigger this button action */
  150. invokeOnClose: false,
  151.  
  152. /* custom button class name */
  153. className: alertify.defaults.theme.cancel,
  154.  
  155. /* custom button attributes */
  156. attrs: {},
  157.  
  158. /* Defines the button scope, either primary (default) or auxiliary */
  159. scope:'auxiliary',
  160.  
  161. /* The will conatin the button DOMElement once buttons are created */
  162. element: undefined
  163. },{
  164. /* button label */
  165. text: '关闭',
  166.  
  167. /*bind a keyboard key to the button */
  168. key: undefined,
  169.  
  170. /* indicate if closing the dialog should trigger this button action */
  171. invokeOnClose: false,
  172.  
  173. /* custom button class name */
  174. className: alertify.defaults.theme.ok,
  175.  
  176. /* custom button attributes */
  177. attrs: {},
  178.  
  179. /* Defines the button scope, either primary (default) or auxiliary */
  180. scope:'primary',
  181.  
  182. /* The will conatin the button DOMElement once buttons are created */
  183. element: undefined
  184. },{
  185. /* button label */
  186. text: '保存',
  187.  
  188. /*bind a keyboard key to the button */
  189. key: undefined,
  190.  
  191. /* indicate if closing the dialog should trigger this button action */
  192. invokeOnClose: false,
  193.  
  194. /* custom button class name */
  195. className: alertify.defaults.theme.ok,
  196.  
  197. /* custom button attributes */
  198. attrs: {},
  199.  
  200. /* Defines the button scope, either primary (default) or auxiliary */
  201. scope:'primary',
  202.  
  203. /* The will conatin the button DOMElement once buttons are created */
  204. element: undefined
  205. }],
  206.  
  207. /* default focus */
  208. focus:{
  209. /* the element to receive default focus, has differnt meaning based on value type:
  210. number: action button index.
  211. string: querySelector to select from dialog body contents.
  212. function: when invoked, should return the focus element.
  213. DOMElement: the focus element.
  214. object: an object that implements .focus() and .select() functions.
  215. */
  216. element: 0,
  217.  
  218. /* indicates if the element should be selected on focus or not*/
  219. select: true
  220.  
  221. },
  222. /* dialog options, these override the defaults */
  223. options: {
  224. title: 'Setting Panel',
  225. modal: true,
  226. basic: false,
  227. frameless: false,
  228. pinned: false,
  229. movable: true,
  230. moveBounded: false,
  231. resizable: true,
  232. autoReset: false,
  233. closable: true,
  234. closableByDimmer: true,
  235. maximizable: false,
  236. startMaximized: false,
  237. pinnable: false,
  238. transition: 'fade',
  239. padding: true,
  240. overflow: true,
  241. /*
  242. onshow:...,
  243. onclose:...,
  244. onfocus:...,
  245. onmove:...,
  246. onmoved:...,
  247. onresize:...,
  248. onresized:...,
  249. onmaximize:...,
  250. onmaximized:...,
  251. onrestore:...,
  252. onrestored:...
  253. */
  254. }
  255. };
  256. },
  257. // This will be called once the dialog DOM has been created, just before its added to the document.
  258. // Its invoked only once.
  259. build:function(){
  260.  
  261. // Do custom DOM manipulation here, accessible via this.elements
  262.  
  263. // this.elements.root ==> Root div
  264. // this.elements.dimmer ==> Modal dimmer div
  265. // this.elements.modal ==> Modal div (dialog wrapper)
  266. // this.elements.dialog ==> Dialog div
  267. // this.elements.reset ==> Array containing the tab reset anchor links
  268. // this.elements.reset[0] ==> First reset element (button).
  269. // this.elements.reset[1] ==> Second reset element (button).
  270. // this.elements.header ==> Dialog header div
  271. // this.elements.body ==> Dialog body div
  272. // this.elements.content ==> Dialog body content div
  273. // this.elements.footer ==> Dialog footer div
  274. // this.elements.resizeHandle ==> Dialog resize handle div
  275.  
  276. // Dialog commands (Pin/Maximize/Close)
  277. // this.elements.commands ==> Object containing dialog command buttons references
  278. // this.elements.commands.container ==> Root commands div
  279. // this.elements.commands.pin ==> Pin command button
  280. // this.elements.commands.maximize ==> Maximize command button
  281. // this.elements.commands.close ==> Close command button
  282.  
  283. // Dialog action buttons (Ok, cancel ... etc)
  284. // this.elements.buttons ==> Object containing dialog action buttons references
  285. // this.elements.buttons.primary ==> Primary buttons div
  286. // this.elements.buttons.auxiliary ==> Auxiliary buttons div
  287.  
  288. // Each created button will be saved with the button definition inside buttons collection
  289. // this.__internal.buttons[x].element
  290.  
  291. },
  292. // This will be called each time the dialog is shown
  293. prepare:function(){
  294. // Do stuff that should be done every time the dialog is shown.
  295. },
  296. // This will be called each time an action button is clicked.
  297. callback:function(closeEvent){
  298. //The closeEvent has the following properties
  299. //
  300. // index: The index of the button triggering the event.
  301. // button: The button definition object.
  302. // cancel: When set true, prevent the dialog from closing.
  303. const myEvent = deepClone(closeEvent);
  304. switch (closeEvent.index) {
  305. case 0: {
  306. // Rests button
  307. closeEvent.cancel = myEvent.cancel = true;
  308. myEvent.save = false;
  309. myEvent.reset = true;
  310. const onreset = this.get('onreset');
  311. typeof onreset === 'function' && onreset(myEvent);
  312. break;
  313. }
  314. case 1: {
  315. // Close button
  316. // Do something here if need
  317. break;
  318. }
  319. case 2: {
  320. // Save button
  321. closeEvent.cancel = myEvent.cancel = true;
  322. myEvent.save = true;
  323. myEvent.reset = false;
  324. const onsave = this.get('onsave');
  325. typeof onsave === 'function' && onsave(myEvent);
  326. }
  327. }
  328. this.get(myEvent.save ? 'saver' : 'reseter').call(this);
  329. closeEvent.cancel = myEvent.cancel;
  330. },
  331. // To make use of AlertifyJS settings API, group your custom settings into a settings object.
  332. settings:{
  333. onsave: function() {},
  334. onreset: function() {},
  335. options: [], // SettingOption array
  336. saver: function() {
  337. this.get('options').forEach((o) => (o.save()));
  338. },
  339. reseter: function() {
  340. this.get('options').forEach((o) => (o.reset()));
  341. }
  342. },
  343. // AlertifyJS will invoke this each time a settings value gets updated.
  344. settingUpdated:function(key, oldValue, newValue){
  345. // Use this to respond to specific setting updates.
  346. const _this = this;
  347. ['onsave', 'onreset', 'saver', 'reseter'].includes(key) && check('function');
  348. ['options'].includes(key) && check(Array);
  349.  
  350. function rollback() {
  351. _this.set(key, oldValue);
  352. }
  353.  
  354. function check(type) {
  355. valid(oldValue, type) && !valid(newValue, type) && rollback();
  356. }
  357.  
  358. function valid(value, type) {
  359. return ({
  360. 'string': () => (typeof value === type),
  361. 'function': () => (value instanceof type)
  362. })[typeof type]();
  363. }
  364. },
  365. // listen to internal dialog events.
  366. hooks:{
  367. // triggered when the dialog is shown, this is seperate from user defined onshow
  368. onshow: function() {
  369. this.resizeTo('80%', '80%');
  370. },
  371. // triggered when the dialog is closed, this is seperate from user defined onclose
  372. onclose: function() {
  373. const onclose = this.get('onclose');
  374. typeof onclose === 'function' && onclose();
  375. },
  376. // triggered when a dialog option gets updated.
  377. // IMPORTANT: This will not be triggered for dialog custom settings updates ( use settingUpdated instead).
  378. onupdate: function() {
  379. }
  380. }
  381. }
  382. }, true);
  383.  
  384. exports = {
  385. SettingPanel: SettingPanel,
  386. SettingOption: SettingOption,
  387. optionAvailable: optionAvailable,
  388. isOption: isOption,
  389. registerElement: registerElement,
  390. };
  391.  
  392. // A table-based setting panel using alertify-js
  393. // For wenku8++ only version
  394. // Use 'new' keyword
  395. // Usage:
  396. /*
  397. var panel = new SettingPanel({
  398. buttons: 0,
  399. header: '',
  400. className: '',
  401. id: '',
  402. name: '',
  403. tables: [
  404. {
  405. className: '',
  406. id: '',
  407. name: '',
  408. rows: [
  409. {
  410. className: '',
  411. id: '',
  412. name: '',
  413. blocks: [
  414. {
  415. isHeader: false,
  416. width: '',
  417. height: '',
  418. innerHTML / innerText: ''
  419. colSpan: 1,
  420. rowSpan: 1,
  421. className: '',
  422. id: '',
  423. name: '',
  424. options: [SettingOption, ...]
  425. children: [HTMLElement, ...]
  426. },
  427. ...
  428. ]
  429. },
  430. ...
  431. ]
  432. },
  433. ...
  434. ]
  435. });
  436. */
  437. function SettingPanel(details={}, storage) {
  438. const SP = this;
  439. SP.insertTable = insertTable;
  440. SP.appendTable = appendTable;
  441. SP.removeTable = removeTable;
  442. SP.remove = remove;
  443. SP.PanelTable = PanelTable;
  444. SP.PanelRow = PanelRow;
  445. SP.PanelBlock = PanelBlock;
  446.  
  447. // <div> element
  448. const elm = $CrE('div');
  449. copyProps(details, elm, ['id', 'name', 'className']);
  450. elm.classList.add('settingpanel-container');
  451.  
  452. // Configure object
  453. let css='', usercss='';
  454. SP.element = elm;
  455. SP.elements = {};
  456. SP.children = {};
  457. SP.tables = [];
  458. SP.length = 0;
  459. details.id !== undefined && (SP.elements[details.id] = elm);
  460. copyProps(details, SP, ['id', 'name']);
  461. Object.defineProperty(SP, 'css', {
  462. configurable: false,
  463. enumerable: true,
  464. get: function() {
  465. return css;
  466. },
  467. set: function(_css) {
  468. addStyle(_css, 'settingpanel-css');
  469. css = _css;
  470. }
  471. });
  472. Object.defineProperty(SP, 'usercss', {
  473. configurable: false,
  474. enumerable: true,
  475. get: function() {
  476. return usercss;
  477. },
  478. set: function(_usercss) {
  479. addStyle(_usercss, 'settingpanel-usercss');
  480. usercss = _usercss;
  481. }
  482. });
  483. SP.css = `.settingpanel-table {border-spacing: 0px; border-collapse: collapse; width: 100%; margin: 2em 0;} .settingpanel-block {border: 1px solid ${ASSETS.Color.Text}; text-align: center; vertical-align: middle; padding: 3px; text-align: left;} .settingpanel-header {font-weight: bold;}`
  484.  
  485. // Make alerity box
  486. const box = SP.alertifyBox = alertify.setpanel({
  487. onsave: function() {
  488. alertify.notify(CONST.Text.Saved);
  489. },
  490. onreset: function() {
  491. alertify.notify(CONST.Text.Reset);
  492. },
  493. buttons: details.hasOwnProperty('buttons') ? details.buttons : 'basic'
  494. });
  495. clearChildNodes(box.elements.content);
  496. box.elements.content.appendChild(elm);
  497. box.elements.content.style.overflow = 'auto';
  498. box.setHeader(details.header);
  499. box.setting({
  500. maximizable: true,
  501. overflow: true
  502. });
  503. !box.isOpen() && box.show();
  504.  
  505. // Create tables
  506. if (details.tables) {
  507. for (const table of details.tables) {
  508. if (table instanceof PanelTable) {
  509. appendTable(table);
  510. } else {
  511. appendTable(new PanelTable(table));
  512. }
  513. }
  514. }
  515.  
  516. // Insert a Panel-Row
  517. // Returns Panel object
  518. function insertTable(table, index) {
  519. // Insert table
  520. !(table instanceof PanelTable) && (table = new PanelTable(table));
  521. index < SP.length ? elm.insertBefore(table.element, elm.children[index]) : elm.appendChild(table.element);
  522. insertItem(SP.tables, table, index);
  523. table.id !== undefined && (SP.children[table.id] = table);
  524. SP.length++;
  525.  
  526. // Set parent
  527. table.parent = SP;
  528.  
  529. // Inherit elements
  530. for (const [id, subelm] of Object.entries(table.elements)) {
  531. SP.elements[id] = subelm;
  532. }
  533.  
  534. // Inherit children
  535. for (const [id, child] of Object.entries(table.children)) {
  536. SP.children[id] = child;
  537. }
  538. return SP;
  539. }
  540.  
  541. // Append a Panel-Row
  542. // Returns Panel object
  543. function appendTable(table) {
  544. return insertTable(table, SP.length);
  545. }
  546.  
  547. // Remove a Panel-Row
  548. // Returns Panel object
  549. function removeTable(index) {
  550. const table = SP.tables[index];
  551. SP.element.removeChild(table.element);
  552. removeItem(SP.rows, index);
  553. return SP;
  554. }
  555.  
  556. // Remove itself from parentElement
  557. // Returns Panel object
  558. function remove() {
  559. SP.element.parentElement && SP.parentElement.removeChild(SP.element);
  560. return SP;
  561. }
  562.  
  563. // Panel-Table object
  564. // Use 'new' keyword
  565. function PanelTable(details={}) {
  566. const PT = this;
  567. PT.insertRow = insertRow;
  568. PT.appendRow = appendRow;
  569. PT.removeRow = removeRow;
  570. PT.remove = remove
  571.  
  572. // <table> element
  573. const elm = $CrE('table');
  574. copyProps(details, elm, ['id', 'name', 'className']);
  575. elm.classList.add('settingpanel-table');
  576.  
  577. // Configure
  578. PT.element = elm;
  579. PT.elements = {};
  580. PT.children = {};
  581. PT.rows = [];
  582. PT.length = 0;
  583. details.id !== undefined && (PT.elements[details.id] = elm);
  584. copyProps(details, PT, ['id', 'name']);
  585.  
  586. // Append rows
  587. if (details.rows) {
  588. for (const row of details.rows) {
  589. if (row instanceof PanelRow) {
  590. insertRow(row);
  591. } else {
  592. insertRow(new PanelRow(row));
  593. }
  594. }
  595. }
  596.  
  597. // Insert a Panel-Row
  598. // Returns Panel-Table object
  599. function insertRow(row, index) {
  600. // Insert row
  601. !(row instanceof PanelRow) && (row = new PanelRow(row));
  602. index < PT.length ? elm.insertBefore(row.element, elm.children[index]) : elm.appendChild(row.element);
  603. insertItem(PT.rows, row, index);
  604. row.id !== undefined && (PT.children[row.id] = row);
  605. PT.length++;
  606.  
  607. // Set parent
  608. row.parent = PT;
  609.  
  610. // Inherit elements
  611. for (const [id, subelm] of Object.entries(row.elements)) {
  612. PT.elements[id] = subelm;
  613. }
  614.  
  615. // Inherit children
  616. for (const [id, child] of Object.entries(row.children)) {
  617. PT.children[id] = child;
  618. }
  619. return PT;
  620. }
  621.  
  622. // Append a Panel-Row
  623. // Returns Panel-Table object
  624. function appendRow(row) {
  625. return insertRow(row, PT.length);
  626. }
  627.  
  628. // Remove a Panel-Row
  629. // Returns Panel-Table object
  630. function removeRow(index) {
  631. const row = PT.rows[index];
  632. PT.element.removeChild(row.element);
  633. removeItem(PT.rows, index);
  634. return PT;
  635. }
  636.  
  637. // Remove itself from parentElement
  638. // Returns Panel-Table object
  639. function remove() {
  640. PT.parent instanceof SettingPanel && PT.parent.removeTable(PT.tables.indexOf(PT));
  641. return PT;
  642. }
  643. }
  644.  
  645. // Panel-Row object
  646. // Use 'new' keyword
  647. function PanelRow(details={}) {
  648. const PR = this;
  649. PR.insertBlock = insertBlock;
  650. PR.appendBlock = appendBlock;
  651. PR.removeBlock = removeBlock;
  652. PR.remove = remove;
  653.  
  654. // <tr> element
  655. const elm = $CrE('tr');
  656. copyProps(details, elm, ['id', 'name', 'className']);
  657. elm.classList.add('settingpanel-row');
  658.  
  659. // Configure object
  660. PR.element = elm;
  661. PR.elements = {};
  662. PR.children = {};
  663. PR.blocks = [];
  664. PR.length = 0;
  665. details.id !== undefined && (PR.elements[details.id] = elm);
  666. copyProps(details, PR, ['id', 'name']);
  667.  
  668. // Append blocks
  669. if (details.blocks) {
  670. for (const block of details.blocks) {
  671. if (block instanceof PanelBlock) {
  672. appendBlock(block);
  673. } else {
  674. appendBlock(new PanelBlock(block));
  675. }
  676. }
  677. }
  678.  
  679. // Insert a Panel-Block
  680. // Returns Panel-Row object
  681. function insertBlock(block, index) {
  682. // Insert block
  683. !(block instanceof PanelBlock) && (block = new PanelBlock(block));
  684. index < PR.length ? elm.insertBefore(block.element, elm.children[index]) : elm.appendChild(block.element);
  685. insertItem(PR.blocks, block, index);
  686. block.id !== undefined && (PR.children[block.id] = block);
  687. PR.length++;
  688.  
  689. // Set parent
  690. block.parent = PR;
  691.  
  692. // Inherit elements
  693. for (const [id, subelm] of Object.entries(block.elements)) {
  694. PR.elements[id] = subelm;
  695. }
  696.  
  697. // Inherit children
  698. for (const [id, child] of Object.entries(block.children)) {
  699. PR.children[id] = child;
  700. }
  701. return PR;
  702. };
  703.  
  704. // Append a Panel-Block
  705. // Returns Panel-Row object
  706. function appendBlock(block) {
  707. return insertBlock(block, PR.length);
  708. }
  709.  
  710. // Remove a Panel-Block
  711. // Returns Panel-Row object
  712. function removeBlock(index) {
  713. const block = PR.blocks[index];
  714. PR.element.removeChild(block.element);
  715. removeItem(PR.blocks, index);
  716. return PR;
  717. }
  718.  
  719. // Remove itself from parent
  720. // Returns Panel-Row object
  721. function remove() {
  722. PR.parent instanceof PanelTable && PR.parent.removeRow(PR.parent.rows.indexOf(PR));
  723. return PR;
  724. }
  725. }
  726.  
  727. // Panel-Block object
  728. // Use 'new' keyword
  729. function PanelBlock(details={}) {
  730. const PB = this;
  731. PB.remove = remove;
  732.  
  733. // <td> element
  734. const elm = $CrE(details.isHeader ? 'th' : 'td');
  735. copyProps(details, elm, ['innerText', 'innerHTML', 'colSpan', 'rowSpan', 'id', 'name', 'className']);
  736. copyProps(details, elm.style, ['width', 'height']);
  737. elm.classList.add('settingpanel-block');
  738. details.isHeader && elm.classList.add('settingpanel-header');
  739.  
  740. // Configure object
  741. PB.element = elm;
  742. PB.elements = {};
  743. PB.children = {};
  744. details.id !== undefined && (PB.elements[details.id] = elm);
  745. copyProps(details, PB, ['id', 'name']);
  746.  
  747. // Append to parent if need
  748. details.parent instanceof PanelRow && (PB.parent = details.parent.appendBlock(PB));
  749.  
  750. // Append SettingOptions if exist
  751. if (details.options) {
  752. details.options.filter(storage ? () => (true) : isOption).map((o) => (isOption(o) ? o : new SettingOption(storage, o))).forEach(function(option) {
  753. SP.alertifyBox.get('options').push(option);
  754. elm.appendChild(option.element);
  755. });
  756. }
  757.  
  758. // Append child elements if exist
  759. if (details.children) {
  760. for (const child of details.children) {
  761. elm.appendChild(child);
  762. }
  763. }
  764.  
  765. // Remove itself from parent
  766. // Returns Panel-Block object
  767. function remove() {
  768. PB.parent instanceof PanelRow && PB.parent.removeBlock(PB.parent.blocks.indexOf(PB));
  769. return PB;
  770. }
  771. }
  772.  
  773. function $R(e) {return $(e) && $(e).parentElement.removeChild($(e));}
  774. function insertItem(arr, item, index) {
  775. for (let i = arr.length; i > index ; i--) {
  776. arr[i] = arr[i-1];
  777. }
  778. arr[index] = item;
  779. return arr;
  780. }
  781. function removeItem(arr, index) {
  782. for (let i = index; i < arr.length-1; i++) {
  783. arr[i] = arr[i+1];
  784. }
  785. delete arr[arr.length-1];
  786. return arr;
  787. }
  788. function MakeReadonlyObj(val) {
  789. return isObject(val) ? new Proxy(val, {
  790. get: function(target, property, receiver) {
  791. return MakeReadonlyObj(target[property]);
  792. },
  793. set: function(target, property, value, receiver) {},
  794. has: function(target, prop) {}
  795. }) : val;
  796.  
  797. function isObject(value) {
  798. return ['object', 'function'].includes(typeof value) && value !== null;
  799. }
  800. }
  801. }
  802.  
  803. // details = {path='config path', type='config type', data='option data'}
  804. function SettingOption(storage, details={}) {
  805. const SO = this;
  806. SO.save = save;
  807. SO.reset = reset;
  808.  
  809. // Initialize ConfigManager
  810. !storage && Err('SettingOption requires GM_storage functions');
  811. const CM = new ConfigManager(CONST.Manager_Config_Ruleset, storage);
  812. const CONFIG = CM.Config;
  813.  
  814. // Get args
  815. const options = ['path', 'type', 'checker', 'data', 'autoSave'];
  816. copyProps(details, SO, options);
  817.  
  818. // Get first available type if multiple types provided
  819. Array.isArray(SO.type) && (SO.type = SO.type.find((t) => (optionAvailable(t))));
  820. !optionAvailable(SO.type) && Err('Unsupported Panel-Option type: ' + details.type);
  821.  
  822. // Create element
  823. const original_value = CM.getConfig(SO.path);
  824. const SOE = {
  825. create: SettingOptionElements[SO.type].createElement.bind(SO),
  826. get: SettingOptionElements[SO.type].getValue.bind(SO),
  827. set: SettingOptionElements[SO.type].setValue.bind(SO),
  828. }
  829. SO.element = SOE.create();
  830. SOE.set(original_value);
  831.  
  832. // Bind change-checker-saver
  833. SO.element.addEventListener('change', function(e) {
  834. if (SO.checker) {
  835. if (SO.checker(e, SOE.get())) {
  836. SO.autoSave && save();
  837. } else {
  838. // Reset value
  839. reset();
  840.  
  841. // Do some value-invalid reminding here
  842. }
  843. } else {
  844. SO.autoSave && save();
  845. }
  846. });
  847.  
  848. function save() {
  849. CM.setConfig(SO.path, SOE.get());
  850. }
  851.  
  852. function reset(save=false) {
  853. SOE.set(original_value);
  854. save && CM.setConfig(SO.path, original_value);
  855. }
  856. }
  857.  
  858. // Check if an settingoption type available
  859. function optionAvailable(type) {
  860. return Object.keys(SettingOptionElements).includes(type);
  861. }
  862.  
  863. // Register SettingOption element
  864. function registerElement(name, obj) {
  865. const formatOkay = typeof obj.createElement === 'function' && typeof obj.setValue === 'function' && typeof obj.getValue === 'function';
  866. const noConflict = !SettingOptionElements.hasOwnProperty(name);
  867. const okay = formatOkay && noConflict;
  868. okay && (SettingOptionElements[name] = obj);
  869. return okay;
  870. }
  871.  
  872. function isOption(obj) {
  873. return obj instanceof SettingOption;
  874. }
  875.  
  876. // Deep copy an object
  877. function deepClone(obj) {
  878. let newObj = Array.isArray(obj) ? [] : {};
  879. if (obj && typeof obj === "object") {
  880. for (let key in obj) {
  881. if (obj.hasOwnProperty(key)) {
  882. newObj[key] = (obj && typeof obj[key] === 'object') ? deepClone(obj[key]) : obj[key];
  883. }
  884. }
  885. }
  886. return newObj;
  887. }
  888. })();

QingJ © 2025

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