Brazen Configuration Manager

Configuration management and related UI creation module

目前为 2024-07-10 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/418665/1408619/Brazen%20Configuration%20Manager.js

  1. // ==UserScript==
  2. // @name Brazen Configuration Manager
  3. // @namespace brazenvoid
  4. // @version 1.5.2
  5. // @author brazenvoid
  6. // @license GPL-3.0-only
  7. // @description Configuration management and related UI creation module
  8. // ==/UserScript==
  9.  
  10. const CONFIG_TYPE_CHECKBOXES_GROUP = 'checkboxes'
  11. const CONFIG_TYPE_FLAG = 'flag'
  12. const CONFIG_TYPE_NUMBER = 'number'
  13. const CONFIG_TYPE_RADIOS_GROUP = 'radios'
  14. const CONFIG_TYPE_RANGE = 'range'
  15. const CONFIG_TYPE_RULESET = 'ruleset'
  16. const CONFIG_TYPE_SELECT = 'select'
  17. const CONFIG_TYPE_TEXT = 'text'
  18.  
  19. class BrazenConfigurationManager
  20. {
  21. /**
  22. * @typedef {{title: string, type: string, element: null|JQuery, value: *, maximum: int, minimum: int, options: string[], helpText: string,
  23. * onFormatForUI: ConfigurationManagerRulesetCallback, onTranslateFromUI: ConfigurationManagerRulesetCallback,
  24. * onOptimize: ConfigurationManagerRulesetCallback, createElement: Function, setFromUserInterface: Function, updateUserInterface: Function,
  25. * optimized?: *}} ConfigurationField
  26. */
  27.  
  28. /**
  29. * @callback ConfigurationManagerRulesetCallback
  30. * @param {*} values
  31. */
  32.  
  33. /**
  34. * @callback ConfigurationManagerTagRulesetSelectorGeneratorCallback
  35. * @param {string} tag
  36. * @return {string}
  37. */
  38.  
  39. /**
  40. * @callback ExternalConfigurationChangeCallback
  41. * @param {BrazenConfigurationManager} manager
  42. */
  43.  
  44. /**
  45. * @param {BrazenUIGenerator} uiGenerator
  46. * @param {ConfigurationManagerTagRulesetSelectorGeneratorCallback|null} tagSelectorGenerator
  47. */
  48. constructor(uiGenerator, tagSelectorGenerator = null)
  49. {
  50. /**
  51. * @type {{}}
  52. * @private
  53. */
  54. this._config = {}
  55.  
  56. /**
  57. * @type {ExternalConfigurationChangeCallback|null}
  58. * @private
  59. */
  60. this._onExternalConfigurationChange = null
  61.  
  62. /**
  63. * @type {LocalStore}
  64. * @private
  65. */
  66. this._localStore = null
  67.  
  68. /**
  69. * @type {LocalStore}
  70. * @private
  71. */
  72. this._localStoreId = null
  73.  
  74. /**
  75. * @type {number}
  76. * @private
  77. */
  78. this._syncedLocalStoreId = 0
  79.  
  80. /**
  81. * @type {ConfigurationManagerTagRulesetSelectorGeneratorCallback|null}
  82. * @private
  83. */
  84. this._tagSelectorGenerator = tagSelectorGenerator
  85.  
  86. /**
  87. * @type BrazenUIGenerator
  88. * @private
  89. */
  90. this._uiGen = uiGenerator
  91. }
  92.  
  93. /**
  94. * @param {BrazenUIGenerator} uiGenerator
  95. * @param {ConfigurationManagerTagRulesetSelectorGeneratorCallback|null} tagSelectorGenerator
  96. * @return {BrazenConfigurationManager}
  97. */
  98. static create(uiGenerator, tagSelectorGenerator = null)
  99. {
  100. return new BrazenConfigurationManager(uiGenerator, tagSelectorGenerator)
  101. }
  102.  
  103. /**
  104. * @param {string} type
  105. * @param {string} name
  106. * @param {*} value
  107. * @param {string|null} helpText
  108. * @return ConfigurationField
  109. * @private
  110. */
  111. _createField(type, name, value, helpText)
  112. {
  113. let fieldKey = this._formatFieldKey(name)
  114. let field = this._config[fieldKey]
  115. if (!field) {
  116. field = {
  117. element: null,
  118. helpText: helpText,
  119. title: name,
  120. type: type,
  121. value: value,
  122. createElement: null,
  123. setFromUserInterface: null,
  124. updateUserInterface: null,
  125. }
  126. this._config[fieldKey] = field
  127. } else {
  128. if (helpText) {
  129. field.helpText = helpText
  130. }
  131. field.value = value
  132. }
  133. return field
  134. }
  135.  
  136. /**
  137. * @param {string} name
  138. * @return {string}
  139. * @private
  140. */
  141. _formatFieldKey(name)
  142. {
  143. return Utilities.toKebabCase(name)
  144. }
  145.  
  146. /**
  147. * @param {boolean} ignoreIfDefaultsSet
  148. * @private
  149. */
  150. _syncLocalStore(ignoreIfDefaultsSet)
  151. {
  152. let field
  153. let storeObject = this._localStore.get()
  154.  
  155. if (!ignoreIfDefaultsSet || !this._localStore.wereDefaultsSet()) {
  156. for (let key in this._config) {
  157.  
  158. field = this._config[key]
  159. if (typeof storeObject[key] !== 'undefined') {
  160.  
  161. field.value = storeObject[key]
  162. if (field.type === CONFIG_TYPE_RULESET) {
  163. field.optimized = Utilities.callEventHandler(field.onOptimize, [field.value])
  164. }
  165. }
  166. }
  167. this.updateInterface()
  168. }
  169. return this
  170. }
  171.  
  172. /**
  173. * @return {{}}
  174. * @private
  175. */
  176. _toStoreObject()
  177. {
  178. let storeObject = {}
  179. for (let key in this._config) {
  180. storeObject[key] = this._config[key].value
  181. }
  182. return storeObject
  183. }
  184.  
  185. /**
  186. * @param id
  187. * @private
  188. */
  189. _updateLocalStoreId(id = null)
  190. {
  191. if (id === null) {
  192. id = Utilities.generateId()
  193. }
  194. this._localStoreId.save({id: id})
  195. this._syncedLocalStoreId = id
  196. }
  197.  
  198. /**
  199. * @param {string} name
  200. * @param {array} keyValuePairs
  201. * @param {string} helpText
  202. * @returns {BrazenConfigurationManager}
  203. */
  204. addCheckboxesGroup(name, keyValuePairs, helpText)
  205. {
  206. let field = this._createField(CONFIG_TYPE_CHECKBOXES_GROUP, name, [], helpText)
  207.  
  208. field.options = keyValuePairs
  209.  
  210. field.createElement = () => {
  211. field.element = this._uiGen.createFormCheckBoxesGroupSection(field.title, field.options, field.helpText)
  212. return field.element
  213. }
  214. field.setFromUserInterface = () => {
  215. field.value = []
  216. field.element.find('input:checked').each((index, element) => {
  217. field.value.push($(element).attr('data-value'))
  218. })
  219. }
  220. field.updateUserInterface = () => {
  221. let elements = field.element.find('input')
  222. for (let key of field.value) {
  223. elements.filter('[data-value="' + key + '"]').prop('checked', true)
  224. }
  225. }
  226. return this
  227. }
  228.  
  229. /**
  230. * @param {string} name
  231. * @param {string} helpText
  232. * @returns {BrazenConfigurationManager}
  233. */
  234. addFlagField(name, helpText)
  235. {
  236. let field = this._createField(CONFIG_TYPE_FLAG, name, false, helpText)
  237.  
  238. field.createElement = () => {
  239. let inputGroup = this._uiGen.createFormInputGroup(field.title, 'checkbox', field.helpText)
  240. field.element = inputGroup.find('input')
  241. return inputGroup
  242. }
  243. field.setFromUserInterface = () => {
  244. field.value = field.element.prop('checked')
  245. }
  246. field.updateUserInterface = () => {
  247. field.element.prop('checked', field.value)
  248. }
  249. return this
  250. }
  251.  
  252. /**
  253. * @param {string} name
  254. * @param {int} minimum
  255. * @param {int} maximum
  256. * @param {string} helpText
  257. * @returns {BrazenConfigurationManager}
  258. */
  259. addNumberField(name, minimum, maximum, helpText)
  260. {
  261. let field = this._createField(CONFIG_TYPE_NUMBER, name, minimum, helpText)
  262.  
  263. field.minimum = minimum
  264. field.maximum = maximum
  265.  
  266. field.createElement = () => {
  267. let inputGroup = this._uiGen.createFormInputGroup(field.title, 'number', field.helpText).
  268. attr('min', field.minimum).
  269. attr('max', field.maximum)
  270. field.element = inputGroup.find('input')
  271. return inputGroup
  272. }
  273. field.setFromUserInterface = () => {
  274. field.value = parseInt(field.element.val())
  275. }
  276. field.updateUserInterface = () => {
  277. field.element.val(field.value)
  278. }
  279. return this
  280. }
  281.  
  282. /**
  283. * @param {string} name
  284. * @param {array} keyValuePairs
  285. * @param {string} helpText
  286. * @returns {BrazenConfigurationManager}
  287. */
  288. addRadiosGroup(name, keyValuePairs, helpText)
  289. {
  290. let field = this._createField(CONFIG_TYPE_RADIOS_GROUP, name, keyValuePairs[0][1], helpText)
  291.  
  292. field.options = keyValuePairs
  293.  
  294. field.createElement = () => {
  295. let inputGroup = this._uiGen.createFormRadiosGroupSection(field.title, field.options, field.helpText)
  296. field.element = inputGroup
  297. return inputGroup
  298. }
  299. field.setFromUserInterface = () => {
  300. field.value = field.element.find('input:checked').attr('data-value')
  301. }
  302. field.updateUserInterface = () => {
  303. field.element.find('input[data-value="' + field.value + '"]').prop('checked', true).trigger('change')
  304. }
  305. return this
  306. }
  307.  
  308. /**
  309. * @param {string} name
  310. * @param {int} minimum
  311. * @param {int} maximum
  312. * @param {string} helpText
  313. * @returns {BrazenConfigurationManager}
  314. */
  315. addRangeField(name, minimum, maximum, helpText)
  316. {
  317. let field = this._createField(CONFIG_TYPE_RANGE, name, {minimum: minimum, maximum: minimum}, helpText)
  318.  
  319. field.minimum = minimum
  320. field.maximum = maximum
  321.  
  322. field.createElement = () => {
  323. let inputGroup = this._uiGen.createFormRangeInputGroup(field.title, 'number', field.minimum, field.maximum,
  324. field.helpText)
  325. field.element = inputGroup.find('input')
  326. return inputGroup
  327. }
  328. field.setFromUserInterface = () => {
  329. field.value = {
  330. minimum: field.element.first().val(),
  331. maximum: field.element.last().val(),
  332. }
  333. }
  334. field.updateUserInterface = () => {
  335. field.element.first().val(field.value.minimum)
  336. field.element.last().val(field.value.maximum)
  337. }
  338. return this
  339. }
  340.  
  341. /**
  342. * @param {string} name
  343. * @param {number} rows
  344. * @param {string|null} helpText
  345. * @param {ConfigurationManagerRulesetCallback} onTranslateFromUI
  346. * @param {ConfigurationManagerRulesetCallback} onFormatForUI
  347. * @param {ConfigurationManagerRulesetCallback} onOptimize
  348. * @return {BrazenConfigurationManager}
  349. */
  350. addRulesetField(name, rows, helpText, onTranslateFromUI = null, onFormatForUI = null, onOptimize = null)
  351. {
  352. let field = this._createField(CONFIG_TYPE_RULESET, name, [], helpText)
  353.  
  354. field.optimized = null
  355. field.onTranslateFromUI = onTranslateFromUI ?? field.onTranslateFromUI
  356. field.onFormatForUI = onFormatForUI ?? field.onFormatForUI
  357. field.onOptimize = onOptimize ?? field.onOptimize
  358.  
  359. field.createElement = () => {
  360. let inputGroup = this._uiGen.createFormTextAreaGroup(field.title, rows, field.helpText)
  361. field.element = inputGroup.find('textarea')
  362. return inputGroup
  363. }
  364. field.setFromUserInterface = () => {
  365. let value = Utilities.trimAndKeepNonEmptyStrings(field.element.val().split(REGEX_LINE_BREAK))
  366. field.value = Utilities.callEventHandler(field.onTranslateFromUI, [value], value)
  367. field.optimized = Utilities.callEventHandler(field.onOptimize, [field.value])
  368. }
  369. field.updateUserInterface = () => {
  370. field.element.val(Utilities.callEventHandler(field.onFormatForUI, [field.value], field.value).join('\n'))
  371. }
  372. return this
  373. }
  374.  
  375. /**
  376. * @param {string} name
  377. * @param {array} keyValuePairs
  378. * @param {string} helpText
  379. * @returns {BrazenConfigurationManager}
  380. */
  381. addSelectField(name, keyValuePairs, helpText)
  382. {
  383. let field = this._createField(CONFIG_TYPE_SELECT, name, keyValuePairs[0][1], helpText)
  384.  
  385. field.options = keyValuePairs
  386.  
  387. field.createElement = () => {
  388. let inputGroup = this._uiGen.createFormRadiosGroupSection(field.title, field.options, field.helpText)
  389. field.element = inputGroup.find('select')
  390. return inputGroup
  391. }
  392. field.setFromUserInterface = () => {
  393. field.value = field.element.val()
  394. }
  395. field.updateUserInterface = () => {
  396. field.element.val(field.value).trigger('change')
  397. }
  398. return this
  399. }
  400.  
  401. /**
  402. * @param {string} name
  403. * @param {number} rows
  404. * @param {string|null} helpText
  405. * @return {BrazenConfigurationManager}
  406. */
  407. addTagRulesetField(name, rows, helpText = null)
  408. {
  409. return this.addRulesetField(name, rows, helpText, null, null, (rules) => {
  410.  
  411. let orTags, iteratedRuleset
  412. let optimizedRuleset = []
  413.  
  414. // Operations
  415.  
  416. let expandRuleset = (ruleset, tags) => {
  417.  
  418. let grownRuleset = []
  419. for (let tag of tags) {
  420. for (let rule of ruleset) {
  421. grownRuleset.push([...rule, this._tagSelectorGenerator(tag)])
  422. }
  423. }
  424. return grownRuleset
  425. }
  426.  
  427. let growRuleset = (ruleset, tagToAdd) => {
  428.  
  429. if (ruleset.length) {
  430. for (let rule of ruleset) {
  431. rule.push(this._tagSelectorGenerator(tagToAdd))
  432. }
  433. } else {
  434. let tags = typeof tagToAdd === 'string' ? [tagToAdd] : tagToAdd
  435. for (let tag of tags) {
  436. ruleset.push([this._tagSelectorGenerator(tag)])
  437. }
  438. }
  439. }
  440.  
  441. // Translate user defined rules
  442.  
  443. for (let blacklistedRule of rules) {
  444.  
  445. iteratedRuleset = []
  446. for (let andTag of blacklistedRule.split('&')) {
  447.  
  448. orTags = andTag.split('|')
  449. if (orTags.length === 1) {
  450. growRuleset(iteratedRuleset, andTag)
  451. } else if (iteratedRuleset.length) {
  452. iteratedRuleset = expandRuleset(iteratedRuleset, orTags)
  453. } else {
  454. growRuleset(iteratedRuleset, orTags)
  455. }
  456. }
  457. optimizedRuleset = optimizedRuleset.concat(iteratedRuleset)
  458. }
  459.  
  460. // Sort rules by complexity
  461.  
  462. return optimizedRuleset.sort((a, b) => a.length - b.length)
  463. })
  464. }
  465.  
  466. /**
  467. * @param {string} name
  468. * @param {string} helpText
  469. * @returns {BrazenConfigurationManager}
  470. */
  471. addTextField(name, helpText)
  472. {
  473. let field = this._createField(CONFIG_TYPE_TEXT, name, '', helpText)
  474.  
  475. field.createElement = () => {
  476. let inputGroup = this._uiGen.createFormInputGroup(field.title, 'text', field.helpText)
  477. field.element = inputGroup.find('input')
  478. return inputGroup
  479. }
  480. field.setFromUserInterface = () => {
  481. field.value = field.element.val()
  482. }
  483. field.updateUserInterface = () => {
  484. field.element.val(field.value)
  485. }
  486. return this
  487. }
  488.  
  489. /**
  490. * @returns {string}
  491. */
  492. backup()
  493. {
  494. let backupConfig = this._toStoreObject()
  495. backupConfig.id = this._syncedLocalStoreId
  496. return Utilities.objectToJSON(backupConfig)
  497. }
  498.  
  499. /**
  500. * @param {string} name
  501. * @returns {JQuery}
  502. */
  503. createElement(name)
  504. {
  505. return this.getFieldOrFail(name).createElement()
  506. }
  507.  
  508. /**
  509. * @param {string} configKey
  510. * @returns {function(*): boolean}
  511. */
  512. generateValidationCallback(configKey)
  513. {
  514. let validationCallback
  515. switch (this.getField(configKey).type) {
  516. case CONFIG_TYPE_FLAG:
  517. case CONFIG_TYPE_RADIOS_GROUP:
  518. case CONFIG_TYPE_SELECT:
  519. validationCallback = (value) => value
  520. break
  521. case CONFIG_TYPE_CHECKBOXES_GROUP:
  522. validationCallback = (valueKeys) => valueKeys.length
  523. break
  524. case CONFIG_TYPE_NUMBER:
  525. validationCallback = (value) => value > 0
  526. break
  527. case CONFIG_TYPE_RANGE:
  528. validationCallback = (range) => range.minimum > 0 || range.maximum > 0
  529. break
  530. case CONFIG_TYPE_RULESET:
  531. validationCallback = (rules) => rules.length
  532. break
  533. case CONFIG_TYPE_TEXT:
  534. validationCallback = (value) => value.length
  535. break
  536. default:
  537. throw new Error('Associated config type requires explicit validation callback definition.')
  538. }
  539. return validationCallback
  540. }
  541.  
  542. /**
  543. * @param {string} name
  544. * @return {ConfigurationField|null}
  545. */
  546. getField(name)
  547. {
  548. return this._config[this._formatFieldKey(name)]
  549. }
  550.  
  551. /**
  552. * @param {string} name
  553. * @return {ConfigurationField}
  554. */
  555. getFieldOrFail(name)
  556. {
  557. let field = this._config[this._formatFieldKey(name)]
  558. if (field) {
  559. return field
  560. }
  561. throw new Error('Field named "' + name + '" could not be found')
  562. }
  563.  
  564. /**
  565. * @param {string} name
  566. * @returns {*}
  567. */
  568. getValue(name)
  569. {
  570. return this.getFieldOrFail(name).value
  571. }
  572.  
  573. /**
  574. * @param {string} name
  575. * @return {boolean}
  576. */
  577. hasField(name)
  578. {
  579. return typeof this.getField(name) !== 'undefined'
  580. }
  581.  
  582. /**
  583. * @param scriptPrefix
  584. * @return {BrazenConfigurationManager}
  585. */
  586. initialize(scriptPrefix)
  587. {
  588. this._localStore = new LocalStore(scriptPrefix + 'settings', this._toStoreObject())
  589. this._localStore.onChange(() => this.updateInterface())
  590.  
  591. this._localStoreId = new LocalStore(scriptPrefix + 'settings-id', {id: Utilities.generateId()})
  592. this._syncedLocalStoreId = this._localStoreId.get().id
  593.  
  594. $(document).on('visibilitychange', () => {
  595. if (!document.hidden && this._syncedLocalStoreId !== this._localStoreId.get().id) {
  596. this._syncLocalStore(true)
  597. Utilities.callEventHandler(this._onExternalConfigurationChange, [this])
  598. }
  599. })
  600. return this._syncLocalStore(true)
  601. }
  602.  
  603. /**
  604. * @param {ExternalConfigurationChangeCallback} eventHandler
  605. * @return {BrazenConfigurationManager}
  606. */
  607. onExternalConfigurationChange(eventHandler)
  608. {
  609. this._onExternalConfigurationChange = eventHandler
  610. return this
  611. }
  612.  
  613. /**
  614. * @param {string} backedUpConfiguration
  615. */
  616. restore(backedUpConfiguration)
  617. {
  618. let backupConfig = Utilities.objectFromJSON(backedUpConfiguration)
  619. let id = backupConfig.id
  620. delete backupConfig.id
  621.  
  622. this._localStore.save(backupConfig)
  623. this._syncLocalStore(false)
  624. this._updateLocalStoreId(id)
  625.  
  626. return this
  627. }
  628.  
  629. /**
  630. * @return {BrazenConfigurationManager}
  631. */
  632. revertChanges()
  633. {
  634. return this._syncLocalStore(false)
  635. }
  636.  
  637. /**
  638. * @return {BrazenConfigurationManager}
  639. */
  640. save()
  641. {
  642. this.update()._localStore.save(this._toStoreObject())
  643. this._updateLocalStoreId()
  644.  
  645. return this
  646. }
  647.  
  648. /**
  649. * @return {BrazenConfigurationManager}
  650. */
  651. update()
  652. {
  653. let field
  654. for (let fieldName in this._config) {
  655. field = this._config[fieldName]
  656. if (field.element) {
  657. field.setFromUserInterface()
  658. }
  659. }
  660. return this
  661. }
  662.  
  663. /**
  664. * @return {BrazenConfigurationManager}
  665. */
  666. updateInterface()
  667. {
  668. let field
  669. for (let fieldName in this._config) {
  670. field = this._config[fieldName]
  671. if (field.element) {
  672. field.updateUserInterface()
  673. }
  674. }
  675. return this
  676. }
  677. }