Brazen Configuration Manager

Configuration management and related UI creation module

目前为 2024-09-20 提交的版本。查看 最新版本

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

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