Greasy Fork 还支持 简体中文。

persistent-state

simple state manager based on Vue3.reactive and localStorage

  1. // ==UserScript==
  2. // @name persistent-state
  3. // @description simple state manager based on Vue3.reactive and localStorage
  4. // @namespace http://tampermonkey.net/
  5. // @author smartacephale
  6. // @license MIT
  7. // @version 1.3
  8. // @match *://*/*
  9. // ==/UserScript==
  10. /* globals reactive watch parseIntegerOr */
  11.  
  12. class PersistentState {
  13. constructor(state, key = "state_acephale") {
  14. this.key = key;
  15. this.state = reactive(state);
  16. this.sync();
  17. this.watchPersistence();
  18. }
  19.  
  20. sync() {
  21. this.trySetFromLocalStorage();
  22. window.addEventListener('focus', this.trySetFromLocalStorage);
  23. }
  24.  
  25. watchPersistence() {
  26. watch(this.state, (value) => {
  27. this.saveToLocalStorage(this.key, value);
  28. });
  29. }
  30.  
  31. saveToLocalStorage(key, value) {
  32. localStorage.setItem(key, JSON.stringify(value));
  33. }
  34.  
  35. trySetFromLocalStorage = () => {
  36. const localStorageValue = localStorage.getItem(this.key);
  37. if (localStorageValue !== null) {
  38. const prevState = JSON.parse(localStorageValue);
  39. for (const prop of Object.keys(prevState)) {
  40. this.state[prop] = prevState[prop];
  41. }
  42. }
  43. }
  44. }
  45.  
  46.  
  47. class DefaultState {
  48. DEFAULT_STATE = {
  49. filterExcludeWords: "",
  50. filterExclude: false,
  51. filterIncludeWords: "",
  52. filterInclude: false,
  53. infiniteScrollEnabled: true,
  54. uiEnabled: true,
  55. };
  56.  
  57. OPTIONAL_FILTERS = {
  58. DURATION_FILTER: {
  59. filterDurationFrom: 0,
  60. filterDurationTo: 600,
  61. filterDuration: false
  62. },
  63. PRIVACY_FILTER: {
  64. filterPrivate: false,
  65. filterPublic: false
  66. }
  67. }
  68.  
  69. DEFAULT_OPTIONS = {
  70. PRIVACY_FILTER: false,
  71. DURATION_FILTER: true
  72. }
  73.  
  74. constructor(options = this.DEFAULT_OPTIONS, custom = {}) {
  75. const opted = Object.assign(this.DEFAULT_OPTIONS, options);
  76. Object.keys(opted).forEach(key => {
  77. if (opted[key]) {
  78. Object.assign(this.DEFAULT_STATE, this.OPTIONAL_FILTERS[key]);
  79. }
  80. });
  81.  
  82. Object.assign(this.DEFAULT_STATE, { custom });
  83. const { state } = new PersistentState(this.DEFAULT_STATE);
  84.  
  85. this.state = state;
  86.  
  87. this.stateLocale = reactive({
  88. pagIndexLast: 1,
  89. pagIndexCur: 1,
  90. filterOptions: opted
  91. });
  92. }
  93.  
  94. setWatchers(applyFilter) {
  95. const { state, stateLocale } = this;
  96.  
  97. if (stateLocale.filterOptions.PRIVACY_FILTER) {
  98. watch(() => state.filterPrivate, () => applyFilter({ filterPrivate: true }));
  99. watch(() => state.filterPublic, () => applyFilter({ filterPublic: true }));
  100. }
  101.  
  102. if (stateLocale.filterOptions.DURATION_FILTER) {
  103. watch([() => state.filterDurationFrom, () => state.filterDurationTo], (a, b) => {
  104. state.filterDurationFrom = parseIntegerOr(a[0], b[0]);
  105. state.filterDurationTo = parseIntegerOr(a[1], b[1]);
  106. if (state.filterDuration) applyFilter({ filterDuration: true });
  107. });
  108. watch(() => state.filterDuration, () => applyFilter({ filterDuration: true }));
  109. }
  110.  
  111. watch(() => state.filterExclude, () => applyFilter({ filterExclude: true }));
  112. watch(() => state.filterExcludeWords, () => {
  113. if (state.filterExclude) applyFilter({ filterExclude: true });
  114. }, { deep: true });
  115.  
  116. watch(() => state.filterInclude, () => applyFilter({ filterInclude: true }));
  117. watch(() => state.filterIncludeWords, () => {
  118. if (state.filterInclude) applyFilter({ filterInclude: true });
  119. }, { deep: true });
  120. }
  121. }