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