persistent-state

simple state manager based on Vue3.reactive and localStorage

当前为 2024-07-01 提交的版本,查看 最新版本

  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.2
  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. constructor(options = {
  70. PRIVACY_FILTER: false,
  71. DURATION_FILTER: true
  72. }) {
  73. Object.keys(options).forEach(key => {
  74. if (options[key]) {
  75. Object.assign(this.DEFAULT_STATE, this.OPTIONAL_FILTERS[key]);
  76. }
  77. });
  78.  
  79. const { state } = new PersistentState(this.DEFAULT_STATE);
  80.  
  81. this.state = state;
  82.  
  83. this.stateLocale = reactive({
  84. pagIndexLast: 1,
  85. pagIndexCur: 1,
  86. });
  87. }
  88.  
  89. setWatchers(applyFilter) {
  90. const { state } = this;
  91.  
  92. if (Object.hasOwn(state, 'filterPrivate')) {
  93. watch(() => state.filterPrivate, () => applyFilter({ filterPrivate: true }));
  94. watch(() => state.filterPublic, () => applyFilter({ filterPublic: true }));
  95. }
  96.  
  97. if (Object.hasOwn(state, 'filterDurationFrom')) {
  98. watch([() => state.filterDurationFrom, () => state.filterDurationTo], (a, b) => {
  99. state.filterDurationFrom = parseIntegerOr(a[0], b[0]);
  100. state.filterDurationTo = parseIntegerOr(a[1], b[1]);
  101. if (state.filterDuration) applyFilter({ filterDuration: true });
  102. });
  103. watch(() => state.filterDuration, () => applyFilter({ filterDuration: true }));
  104. }
  105.  
  106. watch(() => state.filterExclude, () => applyFilter({ filterExclude: true }));
  107. watch(() => state.filterExcludeWords, () => {
  108. if (state.filterExclude) applyFilter({ filterExclude: true });
  109. }, { deep: true });
  110.  
  111. watch(() => state.filterInclude, () => applyFilter({ filterInclude: true }));
  112. watch(() => state.filterIncludeWords, () => {
  113. if (state.filterInclude) applyFilter({ filterInclude: true });
  114. }, { deep: true });
  115. }
  116. }