SettingPanel

SettingPanel for wenku8++

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

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