SettingPanel

SettingPanel for wenku8++

当前为 2022-08-27 提交的版本,查看 最新版本

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