SettingPanel

SettingPanel for wenku8++

目前为 2022-08-26 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/450209/1085920/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.1.7
  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, onunsave){
  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('onunsave', onunsave);
  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: true,
  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. switch (closeEvent.index) {
  195. case 0: {
  196. const onunsave = this.get('onunsave');
  197. typeof onunsave === 'function' && onunsave();
  198. //alertify.alert('Not saved');
  199. break;
  200. }
  201. case 1: {
  202. const onsave = this.get('onsave');
  203. typeof onsave === 'function' && onsave();
  204. //alertify.alert('Saved');
  205. }
  206. }
  207. },
  208. // To make use of AlertifyJS settings API, group your custom settings into a settings object.
  209. settings:{
  210. onsave: function() {},
  211. onunsave: function() {}
  212. },
  213. // AlertifyJS will invoke this each time a settings value gets updated.
  214. settingUpdated:function(key, oldValue, newValue){/*
  215. // Use this to respond to specific setting updates.
  216. switch(key){
  217. case 'myProp':
  218. // Do something when 'myProp' changes
  219. break;
  220. }
  221. */},
  222. // listen to internal dialog events.
  223. hooks:{
  224. // triggered when the dialog is shown, this is seperate from user defined onshow
  225. onshow: function(){
  226. this.resizeTo('80%', '80%');
  227. },
  228. // triggered when the dialog is closed, this is seperate from user defined onclose
  229. onclose: function(){
  230. },
  231. // triggered when a dialog option gets updated.
  232. // IMPORTANT: This will not be triggered for dialog custom settings updates ( use settingUpdated instead).
  233. onupdate: function(){
  234. }
  235. }
  236. }
  237. }, true);
  238.  
  239. exports = {
  240. SettingPanel: SettingPanel,
  241. SettingOption: SettingOption,
  242. isOption: isOption
  243. };
  244.  
  245. // A table-based setting panel using alertify-js
  246. // For wenku8++ only version
  247. // Use 'new' keyword
  248. // Usage:
  249. /*
  250. var panel = new SettingPanel({
  251. header: '';
  252. className: '',
  253. id: '',
  254. name: '',
  255. tables: [
  256. {
  257. className: '',
  258. id: '',
  259. name: '',
  260. rows: [
  261. {
  262. className: '',
  263. id: '',
  264. name: '',
  265. blocks: [
  266. {
  267. isHeader: false,
  268. width: '',
  269. height: '',
  270. innerHTML / innerText: ''
  271. colSpan: 1,
  272. rowSpan: 1,
  273. className: '',
  274. id: '',
  275. name: '',
  276. options: [SettingOption, ...]
  277. children: [HTMLElement, ...]
  278. },
  279. ...
  280. ]
  281. },
  282. ...
  283. ]
  284. },
  285. ...
  286. ]
  287. });
  288. */
  289. function SettingPanel(details={}) {
  290. const SP = this;
  291. SP.insertTable = insertTable;
  292. SP.appendTable = appendTable;
  293. SP.removeTable = removeTable;
  294. SP.remove = remove;
  295. SP.PanelTable = PanelTable;
  296. SP.PanelRow = PanelRow;
  297. SP.PanelBlock = PanelBlock;
  298.  
  299. // <div> element
  300. const elm = $CrE('div');
  301. copyProps(details, elm, ['id', 'name', 'className']);
  302. elm.classList.add('settingpanel-container');
  303.  
  304. // Configure object
  305. let css='', usercss='';
  306. SP.element = elm;
  307. SP.elements = {};
  308. SP.children = {};
  309. SP.tables = [];
  310. SP.length = 0;
  311. details.id !== undefined && (SP.elements[details.id] = elm);
  312. copyProps(details, SP, ['id', 'name']);
  313. Object.defineProperty(SP, 'css', {
  314. configurable: false,
  315. enumerable: true,
  316. get: function() {
  317. return css;
  318. },
  319. set: function(_css) {
  320. addStyle(_css, 'settingpanel-css');
  321. css = _css;
  322. }
  323. });
  324. Object.defineProperty(SP, 'usercss', {
  325. configurable: false,
  326. enumerable: true,
  327. get: function() {
  328. return usercss;
  329. },
  330. set: function(_usercss) {
  331. addStyle(_usercss, 'settingpanel-usercss');
  332. usercss = _usercss;
  333. }
  334. });
  335. 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;}'
  336.  
  337. // Create tables
  338. if (details.tables) {
  339. for (const table of details.tables) {
  340. if (table instanceof PanelTable) {
  341. appendTable(table);
  342. } else {
  343. appendTable(new PanelTable(table));
  344. }
  345. }
  346. }
  347.  
  348. // Make alerity box
  349. const box = SP.alertifyBox = alertify.setpanel ? alertify.setpanel('') : alertify.alert();
  350. clearChildNodes(box.elements.content);
  351. box.elements.content.appendChild(elm);
  352. box.elements.content.style.overflow = 'auto';
  353. box.setHeader(details.header);
  354. box.setting({
  355. maximizable: true,
  356. overflow: true
  357. });
  358. !box.isOpen() && box.show();
  359.  
  360. // Insert a Panel-Row
  361. // Returns Panel object
  362. function insertTable(table, index) {
  363. // Insert table
  364. !(table instanceof PanelTable) && (table = new PanelTable(table));
  365. index < SP.length ? elm.insertBefore(table.element, elm.children[index]) : elm.appendChild(table.element);
  366. insertItem(SP.tables, table, index);
  367. table.id !== undefined && (SP.children[table.id] = table);
  368. SP.length++;
  369.  
  370. // Set parent
  371. table.parent = SP;
  372.  
  373. // Inherit elements
  374. for (const [id, subelm] of Object.entries(table.elements)) {
  375. SP.elements[id] = subelm;
  376. }
  377.  
  378. // Inherit children
  379. for (const [id, child] of Object.entries(table.children)) {
  380. SP.children[id] = child;
  381. }
  382. return SP;
  383. }
  384.  
  385. // Append a Panel-Row
  386. // Returns Panel object
  387. function appendTable(table) {
  388. return insertTable(table, SP.length);
  389. }
  390.  
  391. // Remove a Panel-Row
  392. // Returns Panel object
  393. function removeTable(index) {
  394. const table = SP.tables[index];
  395. SP.element.removeChild(table.element);
  396. removeItem(SP.rows, index);
  397. return SP;
  398. }
  399.  
  400. // Remove itself from parentElement
  401. // Returns Panel object
  402. function remove() {
  403. SP.element.parentElement && SP.parentElement.removeChild(SP.element);
  404. return SP;
  405. }
  406.  
  407. // Panel-Table object
  408. // Use 'new' keyword
  409. function PanelTable(details={}) {
  410. const PT = this;
  411. PT.insertRow = insertRow;
  412. PT.appendRow = appendRow;
  413. PT.removeRow = removeRow;
  414. PT.remove = remove
  415.  
  416. // <table> element
  417. const elm = $CrE('table');
  418. copyProps(details, elm, ['id', 'name', 'className']);
  419. elm.classList.add('settingpanel-table');
  420.  
  421. // Configure
  422. PT.element = elm;
  423. PT.elements = {};
  424. PT.children = {};
  425. PT.rows = [];
  426. PT.length = 0;
  427. details.id !== undefined && (PT.elements[details.id] = elm);
  428. copyProps(details, PT, ['id', 'name']);
  429.  
  430. // Append rows
  431. if (details.rows) {
  432. for (const row of details.rows) {
  433. if (row instanceof PanelRow) {
  434. insertRow(row);
  435. } else {
  436. insertRow(new PanelRow(row));
  437. }
  438. }
  439. }
  440.  
  441. // Insert a Panel-Row
  442. // Returns Panel-Table object
  443. function insertRow(row, index) {
  444. // Insert row
  445. !(row instanceof PanelRow) && (row = new PanelRow(row));
  446. index < PT.length ? elm.insertBefore(row.element, elm.children[index]) : elm.appendChild(row.element);
  447. insertItem(PT.rows, row, index);
  448. row.id !== undefined && (PT.children[row.id] = row);
  449. PT.length++;
  450.  
  451. // Set parent
  452. row.parent = PT;
  453.  
  454. // Inherit elements
  455. for (const [id, subelm] of Object.entries(row.elements)) {
  456. PT.elements[id] = subelm;
  457. }
  458.  
  459. // Inherit children
  460. for (const [id, child] of Object.entries(row.children)) {
  461. PT.children[id] = child;
  462. }
  463. return PT;
  464. }
  465.  
  466. // Append a Panel-Row
  467. // Returns Panel-Table object
  468. function appendRow(row) {
  469. return insertRow(row, PT.length);
  470. }
  471.  
  472. // Remove a Panel-Row
  473. // Returns Panel-Table object
  474. function removeRow(index) {
  475. const row = PT.rows[index];
  476. PT.element.removeChild(row.element);
  477. removeItem(PT.rows, index);
  478. return PT;
  479. }
  480.  
  481. // Remove itself from parentElement
  482. // Returns Panel-Table object
  483. function remove() {
  484. PT.parent instanceof SettingPanel && PT.parent.removeTable(PT.tables.indexOf(PT));
  485. return PT;
  486. }
  487. }
  488.  
  489. // Panel-Row object
  490. // Use 'new' keyword
  491. function PanelRow(details={}) {
  492. const PR = this;
  493. PR.insertBlock = insertBlock;
  494. PR.appendBlock = appendBlock;
  495. PR.removeBlock = removeBlock;
  496. PR.remove = remove;
  497.  
  498. // <tr> element
  499. const elm = $CrE('tr');
  500. copyProps(details, elm, ['id', 'name', 'className']);
  501. elm.classList.add('settingpanel-row');
  502.  
  503. // Configure object
  504. PR.element = elm;
  505. PR.elements = {};
  506. PR.children = {};
  507. PR.blocks = [];
  508. PR.length = 0;
  509. details.id !== undefined && (PR.elements[details.id] = elm);
  510. copyProps(details, PR, ['id', 'name']);
  511.  
  512. // Append blocks
  513. if (details.blocks) {
  514. for (const block of details.blocks) {
  515. if (block instanceof PanelBlock) {
  516. appendBlock(block);
  517. } else {
  518. appendBlock(new PanelBlock(block));
  519. }
  520. }
  521. }
  522.  
  523. // Insert a Panel-Block
  524. // Returns Panel-Row object
  525. function insertBlock(block, index) {
  526. // Insert block
  527. !(block instanceof PanelBlock) && (block = new PanelBlock(block));
  528. index < PR.length ? elm.insertBefore(block.element, elm.children[index]) : elm.appendChild(block.element);
  529. insertItem(PR.blocks, block, index);
  530. block.id !== undefined && (PR.children[block.id] = block);
  531. PR.length++;
  532.  
  533. // Set parent
  534. block.parent = PR;
  535.  
  536. // Inherit elements
  537. for (const [id, subelm] of Object.entries(block.elements)) {
  538. PR.elements[id] = subelm;
  539. }
  540.  
  541. // Inherit children
  542. for (const [id, child] of Object.entries(block.children)) {
  543. PR.children[id] = child;
  544. }
  545. return PR;
  546. };
  547.  
  548. // Append a Panel-Block
  549. // Returns Panel-Row object
  550. function appendBlock(block) {
  551. return insertBlock(block, PR.length);
  552. }
  553.  
  554. // Remove a Panel-Block
  555. // Returns Panel-Row object
  556. function removeBlock(index) {
  557. const block = PR.blocks[index];
  558. PR.element.removeChild(block.element);
  559. removeItem(PR.blocks, index);
  560. return PR;
  561. }
  562.  
  563. // Remove itself from parent
  564. // Returns Panel-Row object
  565. function remove() {
  566. PR.parent instanceof PanelTable && PR.parent.removeRow(PR.parent.rows.indexOf(PR));
  567. return PR;
  568. }
  569. }
  570.  
  571. // Panel-Block object
  572. // Use 'new' keyword
  573. function PanelBlock(details={}) {
  574. const PB = this;
  575. PB.remove = remove;
  576.  
  577. // <td> element
  578. const elm = $CrE(details.isHeader ? 'th' : 'td');
  579. copyProps(details, elm, ['innerText', 'innerHTML', 'colSpan', 'rowSpan', 'id', 'name', 'className']);
  580. copyProps(details, elm.style, ['width', 'height']);
  581. elm.classList.add('settingpanel-block');
  582.  
  583. // Configure object
  584. PB.element = elm;
  585. PB.elements = {};
  586. PB.children = {};
  587. details.id !== undefined && (PB.elements[details.id] = elm);
  588. copyProps(details, PB, ['id', 'name']);
  589.  
  590. // Append to parent if need
  591. details.parent instanceof PanelRow && (PB.parent = details.parent.appendBlock(PB));
  592.  
  593. // Append SettingOptions if exist
  594. if (details.options) {
  595. details.options.filter(isOption).forEach(function(option) {
  596. elm.appendChild(option.element);
  597. });
  598. }
  599.  
  600. // Append child elements if exist
  601. if (details.children) {
  602. for (const child of details.children) {
  603. elm.appendChild(child);
  604. }
  605. }
  606.  
  607. // Remove itself from parent
  608. // Returns Panel-Block object
  609. function remove() {
  610. PB.parent instanceof PanelRow && PB.parent.removeBlock(PB.parent.blocks.indexOf(PB));
  611. return PB;
  612. }
  613. }
  614.  
  615. function $R(e) {return $(e) && $(e).parentElement.removeChild($(e));}
  616. function insertItem(arr, item, index) {
  617. for (let i = arr.length; i > index ; i--) {
  618. arr[i] = arr[i-1];
  619. }
  620. arr[index] = item;
  621. return arr;
  622. }
  623. function removeItem(arr, index) {
  624. for (let i = index; i < arr.length-1; i++) {
  625. arr[i] = arr[i+1];
  626. }
  627. delete arr[arr.length-1];
  628. return arr;
  629. }
  630. function MakeReadonlyObj(val) {
  631. return isObject(val) ? new Proxy(val, {
  632. get: function(target, property, receiver) {
  633. return MakeReadonlyObj(target[property]);
  634. },
  635. set: function(target, property, value, receiver) {},
  636. has: function(target, prop) {}
  637. }) : val;
  638.  
  639. function isObject(value) {
  640. return ['object', 'function'].includes(typeof value) && value !== null;
  641. }
  642. }
  643. }
  644.  
  645. // details = {path='config path', type='config type', data='option data'}
  646. function SettingOption(storage, details={}) {
  647. const SO = this;
  648. SO.save = save;
  649.  
  650. // Initialize ConfigManager
  651. !storage && Err('SettingOption requires GM_storage functions');
  652. const CM = new ConfigManager(CONST.Manager_Config_Ruleset, storage);
  653. const CONFIG = CM.Config;
  654.  
  655. // Get args
  656. const options = ['path', 'type', 'checker', 'data'];
  657. copyProps(details, SO, options);
  658.  
  659. // Create element
  660. const original_value = CM.getConfig(SO.path);
  661. // type => create element
  662. const valueElement = {
  663. 'string': $CrE('input'),
  664. 'number': (() => {const e = $CrE('input'); e.type = 'number'; return e;}) (),
  665. 'boolean': (() => {const e = $CrE('input'); e.type = 'checkbox'; return e;}) (),
  666. 'select': (() => {const e = $CrE('select'); SO.data.forEach((d) => {const o = $CrE('option'); o.innerText = d; e.appendChild(o)}); return e;}) ()
  667. };
  668. // type => set element value
  669. const valueSetter = {
  670. 'string': (elm, val) => (elm.value = val),
  671. 'number': (elm, val) => (elm.value = val),
  672. 'boolean': (elm, val) => (elm.checked = val),
  673. 'select': (elm, val) => (Array.from(elm.children).find((opt) => (opt.value === val)).selected = true),
  674. };
  675. // type => get element value
  676. const valueGetter = {
  677. 'string': (elm) => (elm.value),
  678. 'number': (elm) => (elm.value),
  679. 'boolean': (elm) => (elm.checked ? (SO.hasOwnProperty('data') ? SO.data[0] : false) : (SO.hasOwnProperty('data') ? SO.data[1] : true)),
  680. 'select': (elm) => (elm.value),
  681. }
  682. !Object.keys(valueElement).includes(SO.type) && Err('Unsupported Panel-Option type');
  683. SO.element = valueElement[SO.type];
  684. valueSetter[SO.type](SO.element, original_value);
  685.  
  686. // Bind change-checker-saver
  687. ![false, null].includes(SO.checker) && SO.element.addEventListener('change', function(e) {
  688. if (!SO.checker || SO.checker(e, valueGetter(SO.element))) {
  689. // Allows checker to modify saved value
  690. CM.setConfig(SO.path, valueGetter(SO.element));
  691. } else {
  692. valueSetter[SO.type](SO.element, original_value);
  693. }
  694. });
  695.  
  696. function save() {
  697. CM.setConfig(SO.path, SO.element.value);
  698. }
  699. }
  700.  
  701. function isOption(obj) {
  702. return obj instanceof SettingOption;
  703. }
  704. })();